DEV Community

Cover image for Solution: Furthest Building You Can Reach
seanpgallivan
seanpgallivan

Posted on

Solution: Furthest Building You Can Reach

This is part of a series of Leetcode solution explanations (index). If you liked this solution or found it useful, please like this post and/or upvote my solution post on Leetcode's forums.


Leetcode Problem #1642 (Medium): Furthest Building You Can Reach


Description:


(Jump to: Solution Idea || Code: JavaScript | Python | Java | C++)

You are given an integer array heights representing the heights of buildings, some bricks, and some ladders.

You start your journey from building 0 and move to the next building by possibly using bricks or ladders.

While moving from building i to building i+1 (0-indexed),

  • If the current building's height is greater than or equal to the next building's height, you do not need a ladder or bricks.
  • If the current building's height is less than the next building's height, you can either use one ladder or (h[i+1] - h[i]) bricks.

Return the furthest building index (0-indexed) you can reach if you use the given ladders and bricks optimally.


Examples:

Example 1:
Input: heights = [4,2,7,6,9,14,12], bricks = 5, ladders = 1
Output: 4
Explanation: Starting at building 0, you can follow these steps:
- Go to building 1 without using ladders nor bricks since 4 >= 2.
- Go to building 2 using 5 bricks. You must use either bricks or ladders because 2 < 7.
- Go to building 3 without using ladders nor bricks since 7 >= 6.
- Go to building 4 using your only ladder. You must use either bricks or ladders because 6 < 9.
It is impossible to go beyond building 4 because you do not have any more bricks or ladders.
Visual: Example 1 Visual
Example 2:
Input: heights = [4,12,2,7,3,18,20,3,19], bricks = 10, ladders = 2
Output: 7
Example 3:
Input: heights = [14,3,19,3], bricks = 17, ladders = 0
Output: 3

Constraints:

  • 1 <= heights.length <= 10^5
  • 1 <= heights[i] <= 10^6
  • 0 <= bricks <= 10^9
  • 0 <= ladders <= heights.length

Idea:


(Jump to: Problem Description || Code: JavaScript | Python | Java | C++)

The first realization should be that we always want to use our ladders to their best effect: where they would save us the most amount of bricks. Unfortunately, we can't just save the ladders for the largest gaps in the building heights, because we may not be able to reach them by using bricks.

Since we can't find out how far we can go until we figure out where to place the ladders, and we can't figure out where to put the ladders until we see how far we can go, we'll have to move the ladders around as we go in order to maintain their maximum effect.

To put this in terms of a coding solution, as we iterate through the building heights array (H), we'll want to continuously make sure that the largest L number of positive differences are occupied with ladders, allowing us the best use of our B number of bricks.

In general, this means that we should start iterating through H, ignoring any difference (diff) that is 0 or less, and place a ladder whenever we have a positive difference. Once we've used up all the ladders, then we can start using bricks. If we come across a diff that is larger than the smallest ladder that we're currently using, we should replace that ladder with bricks and move the ladder forwad to the current diff. Otherwise we should use bricks for the current diff.

The second big realization at this point is that we need a min-heap or min priority queue in order to keep track of the heights of the ladders in use, so that we can always take the smallest one, thus keeping the ladders always on the largest diff values.

If at any point we run out bricks, then we can't reach the next building and should return i. If we're able to reach the end of H without running out of bricks, we can return H.length - 1, signifying that we reached the last building.

  • Time Complexity: O(N) where N is the length of H
  • Space Complexity: O(L) to keep track of L number of ladder lengths

Implementation:

The Javascript MinPriorityQueue() npm package isn't as efficient as a min-heap, but it is much more succinct, so I've included both versions of code for comparison.

For C++, the priority_queue defaults to a max order, so we can just invert the sign on the diff values before insertion to effectively turn it into a min priority queue instead.


Javascript Code:


(Jump to: Problem Description || Solution Idea)

w/ MinPriorityQueue():
var furthestBuilding = function(H, B, L) {
    let len = H.length - 1,
        pq = new MinPriorityQueue({priority: x => x})
    for (let i = 0; i < len; i++) {
        let diff = H[i+1] - H[i]
        if (diff > 0) {
            if (L > 0) pq.enqueue(diff), L--
            else if (pq.front() && diff > pq.front().element)
                pq.enqueue(diff), B -= pq.dequeue().element
            else B -= diff
            if (B < 0) return i
        }
    }
    return len
};
Enter fullscreen mode Exit fullscreen mode
w/ Min-Heap:
var furthestBuilding = function(H, B, L) {
    let len = H.length - 1, heap = [,]
    const heapify = val => {
        let i = heap.length, par = i >> 1, temp
        heap.push(val)
        while (heap[par] > heap[i]) {
            temp = heap[par], heap[par] = heap[i], heap[i] = temp
            i = par, par = i >> 1
        }
    }
    const extract = () => {
        if (heap.length === 1) return null
        let top = heap[1], left, right, temp,
            i = 1, child = heap[3] < heap[2] ? 3 : 2
        if (heap.length > 2) heap[1] = heap.pop()
        else heap.pop()
        while (heap[i] > heap[child]) {
            temp = heap[child], heap[child] = heap[i], heap[i] = temp
            i = child, left = i << 1, right = left + 1
            child = heap[right] < heap[left] ? right : left
        }
        return top
    }    
    for (let i = 0; i < len; i++) {
        let diff = H[i+1] - H[i]
        if (diff > 0) {
            if (L > 0) heapify(diff), L--
            else if (heap.length > 1 && diff > heap[1])
                heapify(diff), B -= extract()
            else B -= diff
            if (B < 0) return i
        }
    }
    return len
};
Enter fullscreen mode Exit fullscreen mode

Python Code:


(Jump to: Problem Description || Solution Idea)

class Solution:
    def furthestBuilding(self, H: List[int], B: int, L: int) -> int:
        heap = []
        for i in range(len(H) - 1):
            diff = H[i+1] - H[i]
            if diff > 0:
                if L > 0:
                    heappush(heap, diff)
                    L -= 1
                elif heap and diff > heap[0]:
                    heappush(heap, diff)
                    B -= heappop(heap)
                else: B -= diff
                if B < 0: return i
        return len(H) - 1
Enter fullscreen mode Exit fullscreen mode

Java Code:


(Jump to: Problem Description || Solution Idea)

class Solution {
    public int furthestBuilding(int[] H, int B, int L) {
        int len = H.length - 1;
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int i = 0; i < len; i++) {
            int diff = H[i+1] - H[i];
            if (diff > 0) {
                if (L > 0) {
                    pq.add(diff);
                    L--;
                } else if (pq.size() > 0 && diff > pq.peek()) {
                    pq.add(diff);
                    B -= pq.poll();
                } else B -= diff;
                if (B < 0) return i;
            }
        }
        return len;
    }
}
Enter fullscreen mode Exit fullscreen mode

C++ Code:


(Jump to: Problem Description || Solution Idea)

class Solution {
public:
    int furthestBuilding(vector<int>& H, int B, int L) {
        int len = H.size() - 1;
        priority_queue<int> pq;
        for (int i = 0; i < len; i++) {
            int diff = H[i+1] - H[i];
            if (diff > 0) {
                if (L) pq.push(-diff), L--;
                else if (!pq.empty() && diff > -pq.top())
                    pq.push(-diff), B += pq.top(), pq.pop();
                else B -= diff;
                if (B < 0) return i;
            }
        }
        return len;
    }
};
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
midasxiv profile image
Midas/XIV

I really enjoyed reading this, great article. The best part has to be how you pointed out the npm package shortcomings in JS and the caveats to solve the problem in C++ by inverting the sign before adding to a priority queue.

Collapse
 
seanpgallivan profile image
seanpgallivan

Thanks for the response!

I will admit that I felt a little bit bad about including an entire heap implementation (even if only for one JS solution) without actually going into how the heap structure works, but it really does provide much more efficient processing than the MinPriorityQueue npm, which is also obviously not in the standard library.