Problem
You start at building 0 in a row of buildings. Moving to the next building is free when it is the same height or shorter. When the next building is taller, you must either spend bricks equal to the height difference or use one ladder (which covers any height difference). Given a fixed number of bricks and ladders, find the index of the furthest building you can reach.
- Input: heights = [4, 2, 7, 6, 9, 14, 12], bricks = 5, ladders = 1
- Output: 4
- Explanation: Building 0โ1 is a descent (free). Building 1โ2 climbs 5 โ use the ladder. Building 3โ4 climbs 3 โ use 3 bricks (2 left). Building 4โ5 would need 5 bricks but only 2 remain, so we stop at building 4.
Intuition
Ladders should cover the largest gaps, since a ladder costs the same regardless of height difference while bricks are spent proportionally. The challenge is that future gaps are unknown, so we can't assign ladders upfront. The key insight is to greedily commit ladders as we go, then retroactively swap the cheapest ladder use for bricks whenever we exceed the ladder budget โ a min-heap makes that swap O(log k) at each step.
Approach 1 โ Binary Search + Greedy Check
Binary search on the answer: for a candidate target building, check whether our resources are sufficient to reach it. The check is monotone โ if we can reach building t, we can certainly reach any building before it โ so binary search is valid.
- Binary search
left = 0, right = n - 1; biasmidtowardrightto find the furthest reachable building. - For a given
target, collect all positive gaps inheights[0..target]. - Sort gaps in descending order and assign ladders to the largest
laddersgaps. - Sum the remaining gaps (those not covered by ladders); if the sum fits in
bricks,targetis reachable. - Narrow the search window: if reachable, move
left = mid; otherwiseright = mid - 1.
1def furthestBuilding(heights: list[int], bricks: int, ladders: int) -> int:
2 def can_reach(target: int) -> bool:
3 gaps = sorted(
4 (heights[i + 1] - heights[i] for i in range(target) if heights[i + 1] > heights[i]),
5 reverse=True,
6 )
7 # ladders absorb the largest gaps; everything beyond the ladder budget needs bricks
8 brick_cost = sum(gaps[ladders:])
9 return brick_cost <= bricks
10
11 left, right = 0, len(heights) - 1
12 while left < right:
13 mid = left + (right - left + 1) // 2 # ceiling bias so left = mid makes progress
14 if can_reach(mid):
15 left = mid
16 else:
17 right = mid - 1
18
19 return leftTime: O(n log n log n) โ the binary search runs O(log n) iterations; each can_reach call sorts up to n gaps in O(n log n).
Space: O(n) โ temporary gap array holds at most n entries per can_reach call.
Approach 2 โ Greedy with Min-Heap (Single Pass)
Greedily assign a ladder to every climb as we walk forward. When we've used more ladders than allowed, pull the smallest-ladder gap from the heap and pay bricks for it instead โ that swap costs the least bricks. If bricks go negative, we cannot reach the next building.
- Initialize a min-heap to track gap sizes where ladders are tentatively placed.
- For each building, compute the height difference to the next one.
- If the difference is โค 0, move freely โ no resources needed.
- Otherwise, push the gap onto the heap (tentatively use a ladder there).
- If heap size exceeds
ladders, pop the minimum โ convert that cheapest ladder assignment to bricks. - If bricks go negative after the swap, return the current index; we can't proceed.
- If we finish the loop without running out, return the last building index.
1import heapq
2
3def furthestBuilding(heights: list[int], bricks: int, ladders: int) -> int:
4 ladder_heap = [] # min-heap: tracks gap sizes where ladders are currently assigned
5
6 for i in range(len(heights) - 1):
7 diff = heights[i + 1] - heights[i]
8 if diff <= 0:
9 continue # descending or flat โ free to cross
10
11 heapq.heappush(ladder_heap, diff) # tentatively place a ladder on this gap
12
13 if len(ladder_heap) > ladders:
14 # we've overdrawn our ladder budget; swap the cheapest ladder gap to bricks
15 smallest_gap = heapq.heappop(ladder_heap)
16 bricks -= smallest_gap
17 if bricks < 0:
18 return i # can't afford even the cheapest swap โ stuck at building i
19
20 return len(heights) - 1 # survived every climbTime: O(n log k) โ each of the n buildings triggers at most one push and one pop on a heap bounded to size k (the number of ladders). Space: O(k) โ the heap holds at most k gap values simultaneously.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Binary Search + Sort | O(n log n log n) | O(n) | When a simpler, verifiable check function is more important than raw speed |
| Greedy Min-Heap | O(n log k) | O(k) | Default โ single pass, optimal when k (ladders) is small relative to n |
Common Mistakes
- Ignoring descents: Subtracting
heights[i+1] - heights[i]without checking the sign will treat downhill moves as negative brick costs, corrupting the running total. Always skip whendiff <= 0. - Returning
i + 1instead ofion failure: When the swap at stepi(the move from buildingitoi + 1) drains bricks below zero, the furthest reachable building isiโ noti + 1, which is where we failed to arrive. - Using a max-heap instead of a min-heap: The swap should replace the cheapest ladder assignment with bricks, because that minimizes brick expenditure. A max-heap pops the most expensive gap, which is exactly backwards.
- Ceiling-bias omission in binary search: Using
mid = (left + right) // 2(floor) combined withleft = midcauses an infinite loop whenleft + 1 == right. The correct pattern for "find rightmost True" ismid = left + (right - left + 1) // 2. - Integer overflow in the binary search check: The total brick cost across all gaps can reach 10^5 ร 10^6 = 10^11, which overflows a 32-bit integer. Use
longin Java andlong longin C++ for the accumulator.
Related Problems
Last Stone Weightโ greedy with a max-heap; each step removes and reinserts the two largest elements, the same heap-driven selection pattern in reverseKth Largest Element in an Arrayโ maintaining a min-heap of size k to track the k largest values seen so farMerge K Sorted Listsโ min-heap used to greedily select the next smallest element across k sequencesK Closest Points to Originโ bounded-size heap where exceeding the limit triggers a pop, exactly the same heap management loopTotal Cost to Hire K Workersโ greedy selection using two min-heaps, minimizing cost over k rounds of picking