The key observation is that ladders are most valuable for the largest height differences, since a ladder covers any gap regardless of size while bricks are consumed proportionally. For each position we reach, we can check if it's achievable by sorting all the height jumps encountered so far and using ladders for the largest ones while using bricks for the rest.
i from 1 to n - 1.i, we can definitely reach it.i.ladders ones).i - 1.n - 1.class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
n = len(heights)
for i in range(1, n):
if ladders >= i:
continue
diffs = []
for j in range(i):
if heights[j + 1] > heights[j]:
diffs.append(heights[j + 1] - heights[j])
diffs.sort()
brickSum = 0
for j in range(len(diffs) - ladders):
brickSum += diffs[j]
if brickSum > bricks:
return i - 1
return n - 1Instead of checking each building sequentially, we can use binary search to find the furthest reachable building. The key insight is that reachability is monotonic: if we can reach building i, we can also reach all buildings before it. This lets us binary search on the answer.
[ladders - 1, n - 1].mid index, check if we can reach it using canReach(mid).canReach: collect all positive height differences up to index mid.ladders).l - 1 as the furthest reachable building.class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
def canReach(mid):
diffs = []
for i in range(mid):
if heights[i + 1] > heights[i]:
diffs.append(heights[i + 1] - heights[i])
diffs.sort()
brickSum = 0
for j in range(len(diffs) - ladders):
brickSum += diffs[j]
if brickSum > bricks:
return False
return True
l, r = ladders - 1, len(heights) - 1
while l <= r:
mid = (l + r) // 2
if canReach(mid):
l = mid + 1
else:
r = mid - 1
return l - 1We can optimize the previous approach by pre-sorting all height differences once instead of sorting repeatedly for each binary search check. By storing differences with their indices and sorting them in descending order, we can efficiently determine for any target building which jumps to cover with ladders (the largest ones) and which to cover with bricks.
1 to n - 1.mid index, iterate through sorted differences: use ladders for the largest jumps (up to ladders count), and use bricks for the rest that occur before or at the mid index.class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
diffs = []
for i in range(1, len(heights)):
if heights[i] > heights[i - 1]:
diffs.append((heights[i] - heights[i - 1], i))
diffs.sort(reverse=True)
def canReach(index):
useLadders = useBricks = 0
for diff, i in diffs:
if i > index:
continue
if useLadders < ladders:
useLadders += 1
else:
useBricks += diff
if useBricks > bricks:
return False
return True
l, r = 1, len(heights) - 1
while l <= r:
mid = (l + r) >> 1
if canReach(mid):
l = mid + 1
else:
r = mid - 1
return l - 1We can make greedy decisions as we traverse by initially using bricks for each jump, then retroactively swapping to a ladder when bricks run out. A max-heap tracks all brick usages so far, allowing us to efficiently swap the largest brick usage with a ladder when needed. This ensures ladders always cover the biggest gaps.
bricks and push the difference onto a max-heap.bricks go negative:bricks.ladders.n - 1.class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
heap = [] # Max heap of bricks used
for i in range(len(heights) - 1):
diff = heights[i + 1] - heights[i]
if diff <= 0:
continue
bricks -= diff
heapq.heappush(heap, -diff)
if bricks < 0:
if ladders == 0:
return i
ladders -= 1
bricks += -heapq.heappop(heap)
return len(heights) - 1Instead of tracking brick usages, we can track ladder usages in a min-heap. We greedily assign ladders to each jump, but once we've used more than the allowed number of ladders, we convert the smallest ladder usage back to bricks. This way, ladders always end up covering the largest jumps.
bricks.bricks go negative, return the current index.n - 1.class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
minHeap = []
for i in range(len(heights) - 1):
diff = heights[i + 1] - heights[i]
if diff <= 0:
continue
heapq.heappush(minHeap, diff)
if len(minHeap) > ladders:
bricks -= heapq.heappop(minHeap)
if bricks < 0:
return i
return len(heights) - 1Not all building transitions require resources. When heights[i+1] <= heights[i], no bricks or ladders are needed. Forgetting to skip these cases leads to wasting resources on non-existent climbs and incorrect answers. Always check if the height difference is positive before consuming any resources.
The greedy insight is that ladders should cover the largest height differences since they handle any gap regardless of size. Using ladders greedily for the first gaps encountered (rather than the largest ones) leads to suboptimal resource usage. The heap-based solutions ensure ladders always end up covering the maximum height differences.
When bricks become negative, the current building cannot be reached, so the answer should be the previous index. A common mistake is returning i instead of i - 1 in the brute force approach, or returning i when we should return i (the last reachable building) in the heap approach. Pay careful attention to what index represents the last successfully reached building.