1642. Furthest Building You Can Reach
Problem Description
You are given an array heights
where each element represents the height of a building. You start at building 0 and want to travel as far as possible to the right, moving from one building to the next. You have a certain number of bricks
and ladders
to help you.
When moving from building i
to building i+1
:
- If the current building's height is greater than or equal to the next building's height, you can move freely without using any resources
- If the current building's height is less than the next building's height (you need to climb up), you have two options:
- Use one ladder (regardless of the height difference)
- Use exactly
heights[i+1] - heights[i]
bricks
Your goal is to find the furthest building index you can reach by using your bricks and ladders optimally.
For example, if heights = [4, 2, 7, 6, 9, 14, 12]
, bricks = 5
, and ladders = 1
:
- From building 0 (height 4) to building 1 (height 2): Free move (going down)
- From building 1 (height 2) to building 2 (height 7): Need to climb 5 units - could use 5 bricks or 1 ladder
- From building 2 (height 7) to building 3 (height 6): Free move (going down)
- And so on...
The key insight is that you want to use ladders for the largest height differences and bricks for smaller ones to maximize how far you can travel. The solution uses a min-heap to track the smallest climbs where ladders were used, allowing you to swap a ladder for bricks when encountering a larger climb later.
Intuition
The key observation is that ladders are more valuable than bricks because a ladder can overcome any height difference while bricks are consumed based on the actual height difference. This means we should use ladders for the largest gaps and bricks for smaller ones.
But here's the challenge: we don't know all the height differences ahead of time as we traverse the buildings. If we encounter a small gap early on and use a ladder, we might regret it later when we face a much larger gap that could have been a better use of that ladder.
The breakthrough insight is to use a greedy approach with regret:
- Initially, use ladders for the first few climbs we encounter (up to the number of ladders we have)
- Keep track of these climbs in a min-heap
- When we run out of ladders and face a new climb, we can make a smart decision: compare this new climb with the smallest climb where we previously used a ladder
- If we previously used a ladder on a smaller climb, we can "take back" that decision - replace that ladder usage with bricks and use the ladder for the current climb instead
This way, we're always ensuring that our ladders are being used for the largest climbs we've seen so far. The min-heap allows us to efficiently track and retrieve the smallest climb where we've used a ladder.
The algorithm naturally terminates when we either:
- Run out of bricks (can't afford the next climb even with optimal ladder placement)
- Successfully reach the last building
This greedy strategy with the ability to revise past decisions guarantees we'll reach the furthest possible building.
Learn more about Greedy and Heap (Priority Queue) patterns.
Solution Approach
The implementation uses a min-heap data structure combined with a greedy algorithm to solve this problem optimally.
Here's how the algorithm works step by step:
-
Initialize an empty min-heap
h
to store the height differences where we've used ladders. -
Iterate through consecutive buildings from index 0 to
len(heights) - 2
:- Calculate the height difference
d = heights[i+1] - heights[i]
- If
d <= 0
, we can move freely (going down or same level), so continue to the next building
- Calculate the height difference
-
For each upward climb (when
d > 0
):- Push the height difference into the min-heap:
heappush(h, d)
- This represents using a ladder for this climb initially
- Push the height difference into the min-heap:
-
Check if we've used more ladders than available:
- If
len(h) > ladders
, we need to replace one ladder usage with bricks - Pop the smallest height difference from the heap:
smallest = heappop(h)
- This is the most economical climb to use bricks on instead of a ladder
- Deduct bricks:
bricks -= smallest
- If
-
Check if we have enough bricks:
- If
bricks < 0
, we can't make this climb, so return the current indexi
- Otherwise, continue to the next building
- If
-
If we complete the loop, we've successfully reached the last building, so return
len(heights) - 1
Why this works:
- The heap always maintains the
ladders
largest climbs we've encountered so far - Any climb not in the heap is covered by bricks
- By always keeping the largest climbs for ladders and using bricks for smaller ones, we minimize brick usage
- This greedy approach with dynamic reallocation ensures optimal resource usage
Time Complexity: O(n log k)
where n is the number of buildings and k is the number of ladders (due to heap operations)
Space Complexity: O(k)
for storing at most k elements in the heap
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the algorithm works:
Input: heights = [4, 2, 7, 6, 9, 14, 12]
, bricks = 5
, ladders = 1
Step-by-step execution:
Initial state: Min-heap h = []
, at building 0 (height 4)
Move 0→1:
- Height difference:
2 - 4 = -2
(going down) - No resources needed, move freely
- Current position: building 1
Move 1→2:
- Height difference:
7 - 2 = 5
(climbing up) - Push 5 into heap:
h = [5]
- Heap size (1) ≤ ladders (1), so we're using our 1 ladder here
- Current position: building 2
Move 2→3:
- Height difference:
6 - 7 = -1
(going down) - No resources needed, move freely
- Current position: building 3
Move 3→4:
- Height difference:
9 - 6 = 3
(climbing up) - Push 3 into heap:
h = [3, 5]
(min-heap, so 3 is at root) - Heap size (2) > ladders (1), need to use bricks for something
- Pop smallest climb from heap:
heappop() = 3
- Now
h = [5]
(ladder used for the climb of 5) - Use bricks for the climb of 3:
bricks = 5 - 3 = 2
- Bricks ≥ 0, so continue
- Current position: building 4
Move 4→5:
- Height difference:
14 - 9 = 5
(climbing up) - Push 5 into heap:
h = [5, 5]
- Heap size (2) > ladders (1), need to use bricks for something
- Pop smallest climb from heap:
heappop() = 5
- Now
h = [5]
(ladder used for one climb of 5) - Use bricks for the other climb of 5:
bricks = 2 - 5 = -3
- Bricks < 0, cannot make this climb!
- Return current position: 4
Result: The furthest building we can reach is at index 4 (height 9).
Key insights from this example:
- The algorithm initially assigns ladders to climbs as we encounter them
- When we exceed our ladder count, we reassign the ladder from the smallest climb to the current climb if beneficial
- The min-heap ensures we always use ladders for the largest climbs encountered so far
- We stop when we can't afford the next climb with our remaining bricks
Solution Implementation
1class Solution:
2 def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
3 """
4 Find the furthest building that can be reached given limited bricks and ladders.
5 Strategy: Use ladders for the largest jumps, bricks for smaller ones.
6 """
7 import heapq
8
9 # Min heap to track the height differences where we've used ladders
10 min_heap = []
11
12 # Iterate through each consecutive pair of buildings
13 for i in range(len(heights) - 1):
14 current_height = heights[i]
15 next_height = heights[i + 1]
16 height_diff = next_height - current_height
17
18 # Only need resources when going up
19 if height_diff > 0:
20 # Add this jump to heap (initially assume we use a ladder)
21 heapq.heappush(min_heap, height_diff)
22
23 # If we've used more ladders than available
24 if len(min_heap) > ladders:
25 # Replace the smallest ladder usage with bricks
26 smallest_jump = heapq.heappop(min_heap)
27 bricks -= smallest_jump
28
29 # If we don't have enough bricks, can't proceed
30 if bricks < 0:
31 return i
32
33 # If we complete the loop, we can reach the last building
34 return len(heights) - 1
35
1class Solution {
2 public int furthestBuilding(int[] heights, int bricks, int ladders) {
3 // Min heap to store the height differences where we use ladders
4 // We keep the smallest differences in the heap (for ladder usage)
5 PriorityQueue<Integer> minHeap = new PriorityQueue<>();
6
7 int buildingCount = heights.length;
8
9 // Iterate through all adjacent building pairs
10 for (int i = 0; i < buildingCount - 1; i++) {
11 int currentHeight = heights[i];
12 int nextHeight = heights[i + 1];
13 int heightDifference = nextHeight - currentHeight;
14
15 // Only need resources when moving to a taller building
16 if (heightDifference > 0) {
17 // Add this height difference to our ladder usage
18 minHeap.offer(heightDifference);
19
20 // If we've used more ladders than available
21 if (minHeap.size() > ladders) {
22 // Use bricks for the smallest height difference instead
23 // (remove from ladder usage and use bricks)
24 int smallestDifference = minHeap.poll();
25 bricks -= smallestDifference;
26
27 // If we don't have enough bricks, we can't proceed
28 if (bricks < 0) {
29 return i;
30 }
31 }
32 }
33 }
34
35 // Successfully reached the last building
36 return buildingCount - 1;
37 }
38}
39
1class Solution {
2public:
3 int furthestBuilding(vector<int>& heights, int bricks, int ladders) {
4 // Min heap to store the height differences where we use ladders
5 // We want to use ladders for the largest jumps, so we maintain
6 // the smallest jumps in a min heap of size 'ladders'
7 priority_queue<int, vector<int>, greater<int>> minHeap;
8
9 int numBuildings = heights.size();
10
11 // Iterate through consecutive buildings
12 for (int i = 0; i < numBuildings - 1; ++i) {
13 int currentHeight = heights[i];
14 int nextHeight = heights[i + 1];
15 int heightDifference = nextHeight - currentHeight;
16
17 // Only need resources when going up
18 if (heightDifference > 0) {
19 // Add this jump to our heap (assuming we use a ladder for it)
20 minHeap.push(heightDifference);
21
22 // If we've used more ladders than available,
23 // we must use bricks for the smallest jump instead
24 if (minHeap.size() > ladders) {
25 // Use bricks for the smallest jump (top of min heap)
26 bricks -= minHeap.top();
27 minHeap.pop();
28
29 // Check if we have enough bricks
30 if (bricks < 0) {
31 return i; // Can't reach the next building
32 }
33 }
34 }
35 }
36
37 // Successfully reached the last building
38 return numBuildings - 1;
39 }
40};
41
1function furthestBuilding(heights: number[], bricks: number, ladders: number): number {
2 // Min heap to store the height differences where we use ladders
3 // We want to use ladders for the largest jumps, so we maintain
4 // the smallest jumps in a min heap of size 'ladders'
5 const minHeap: number[] = [];
6
7 // Helper functions for heap operations
8 const heapPush = (heap: number[], value: number): void => {
9 heap.push(value);
10 let currentIndex = heap.length - 1;
11
12 // Bubble up to maintain min heap property
13 while (currentIndex > 0) {
14 const parentIndex = Math.floor((currentIndex - 1) / 2);
15 if (heap[currentIndex] >= heap[parentIndex]) break;
16
17 // Swap with parent
18 [heap[currentIndex], heap[parentIndex]] = [heap[parentIndex], heap[currentIndex]];
19 currentIndex = parentIndex;
20 }
21 };
22
23 const heapPop = (heap: number[]): number => {
24 if (heap.length === 0) return -1;
25 if (heap.length === 1) return heap.pop()!;
26
27 const minValue = heap[0];
28 heap[0] = heap.pop()!;
29
30 // Bubble down to maintain min heap property
31 let currentIndex = 0;
32 while (true) {
33 const leftChildIndex = 2 * currentIndex + 1;
34 const rightChildIndex = 2 * currentIndex + 2;
35 let smallestIndex = currentIndex;
36
37 if (leftChildIndex < heap.length && heap[leftChildIndex] < heap[smallestIndex]) {
38 smallestIndex = leftChildIndex;
39 }
40 if (rightChildIndex < heap.length && heap[rightChildIndex] < heap[smallestIndex]) {
41 smallestIndex = rightChildIndex;
42 }
43
44 if (smallestIndex === currentIndex) break;
45
46 // Swap with smallest child
47 [heap[currentIndex], heap[smallestIndex]] = [heap[smallestIndex], heap[currentIndex]];
48 currentIndex = smallestIndex;
49 }
50
51 return minValue;
52 };
53
54 const numBuildings = heights.length;
55
56 // Iterate through consecutive buildings
57 for (let i = 0; i < numBuildings - 1; i++) {
58 const currentHeight = heights[i];
59 const nextHeight = heights[i + 1];
60 const heightDifference = nextHeight - currentHeight;
61
62 // Only need resources when going up
63 if (heightDifference > 0) {
64 // Add this jump to our heap (assuming we use a ladder for it)
65 heapPush(minHeap, heightDifference);
66
67 // If we've used more ladders than available,
68 // we must use bricks for the smallest jump instead
69 if (minHeap.length > ladders) {
70 // Use bricks for the smallest jump (top of min heap)
71 const smallestJump = heapPop(minHeap);
72 bricks -= smallestJump;
73
74 // Check if we have enough bricks
75 if (bricks < 0) {
76 return i; // Can't reach the next building
77 }
78 }
79 }
80 }
81
82 // Successfully reached the last building
83 return numBuildings - 1;
84}
85
Time and Space Complexity
Time Complexity: O(n log k)
where n
is the length of the heights array and k
is the number of ladders.
- The code iterates through the heights array once, which takes
O(n)
time. - For each height difference that is positive (requiring either bricks or a ladder), we perform a heap push operation:
heappush(h, d)
, which takesO(log k)
time since the heap size is at mostk + 1
(number of ladders plus one). - When the heap size exceeds the number of ladders, we perform a heap pop operation:
heappop(h)
, which also takesO(log k)
time. - In the worst case, we perform heap operations for each building transition, resulting in
O(n log k)
total time complexity.
Space Complexity: O(k)
where k
is the number of ladders.
- The heap
h
stores at mostladders + 1
elements at any given time. - When the heap size exceeds
ladders
, we immediately pop the smallest element, maintaining the size constraint. - Therefore, the space complexity is
O(k)
for storing the heap. - The remaining variables (
i
,a
,b
,d
) use constant spaceO(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling Downward or Level Moves
Pitfall: A common mistake is to process all height differences, including negative ones (going down) or zero differences (same level), which unnecessarily complicates the logic and can lead to incorrect resource allocation.
# INCORRECT - Processing all differences
for i in range(len(heights) - 1):
height_diff = heights[i + 1] - heights[i]
heapq.heappush(min_heap, height_diff) # Wrong! Includes negative values
if len(min_heap) > ladders:
bricks -= heapq.heappop(min_heap)
if bricks < 0:
return i
Solution: Always check if height_diff > 0
before processing. Only upward climbs require resources:
# CORRECT - Only process upward climbs if height_diff > 0: heapq.heappush(min_heap, height_diff) # ... rest of the logic
2. Forgetting to Import heapq or Using Wrong Import Statement
Pitfall: The code uses heapq
functions but might forget to import the module, or import it incorrectly when used within a method.
# INCORRECT - Missing import or wrong scope
class Solution:
def furthestBuilding(self, heights, bricks, ladders):
min_heap = []
heappush(min_heap, 5) # NameError: name 'heappush' is not defined
Solution: Import heapq at the beginning of the method or use the full module name:
# CORRECT - Option 1: Import inside method
def furthestBuilding(self, heights, bricks, ladders):
import heapq
min_heap = []
heapq.heappush(min_heap, 5)
# CORRECT - Option 2: Import at class level
import heapq
class Solution:
def furthestBuilding(self, heights, bricks, ladders):
heapq.heappush(min_heap, 5)
3. Off-by-One Error in Loop Range
Pitfall: Using range(len(heights))
instead of range(len(heights) - 1)
, which causes an index out of bounds error when accessing heights[i + 1]
.
# INCORRECT - Will cause IndexError
for i in range(len(heights)): # Goes from 0 to len(heights)-1
height_diff = heights[i + 1] - heights[i] # Error when i = len(heights)-1
Solution: Always use range(len(heights) - 1)
when comparing consecutive elements:
# CORRECT
for i in range(len(heights) - 1):
height_diff = heights[i + 1] - heights[i]
4. Using Max Heap Instead of Min Heap
Pitfall: Misunderstanding the algorithm and trying to use a max heap to keep track of the largest jumps, which complicates the logic unnecessarily.
# INCORRECT - Overcomplicating with max heap
min_heap = []
heapq.heappush(min_heap, -height_diff) # Negating for max heap
if len(min_heap) > ladders:
bricks -= -heapq.heappop(min_heap) # Need to negate back
Solution: Use a min heap directly. The algorithm naturally keeps the largest jumps in the heap when the heap size equals the number of ladders:
# CORRECT - Simple min heap
heapq.heappush(min_heap, height_diff)
if len(min_heap) > ladders:
bricks -= heapq.heappop(min_heap) # Pops the smallest
5. Returning Wrong Index When Resources Are Exhausted
Pitfall: Returning i + 1
or i - 1
instead of i
when we can't make a jump, leading to incorrect results.
# INCORRECT if bricks < 0: return i - 1 # Wrong! We're already at building i # or return i + 1 # Wrong! We can't reach i+1
Solution: Return i
directly - this is the last building we could successfully reach:
# CORRECT if bricks < 0: return i # This is the furthest we can go
How does quick sort divide the problem into subproblems?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!