[LeetCode] 871. Minimum Number of Refueling Stops 最少的加油站個數



A car travels from a starting position to a destination which is `target` miles east of the starting position.

Along the way, there are gas stations.  Each station[i] represents a gas station that is station[i][0] miles east of the starting position, and has station[i][1] liters of gas.

The car starts with an infinite tank of gas, which initially has startFuel liters of fuel in it.  It uses 1 liter of gas per 1 mile that it drives.

When the car reaches a gas station, it may stop and refuel, transferring all the gas from the station into the car.

What is the least number of refueling stops the car must make in order to reach its destination?  If it cannot reach the destination, return -1.

Note that if the car reaches a gas station with 0 fuel left, the car can still refuel there.  If the car reaches the destination with 0 fuel left, it is still considered to have arrived.

Example 1:

Input: target = 1, startFuel = 1, stations = []
Output: 0
Explanation: We can reach the target without refueling.

Example 2:

Input: target = 100, startFuel = 1, stations = [[10,100]]
Output: -1
Explanation: We can't reach the target (or even the first gas station).

Example 3:

Input: target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
Output: 2
Explanation:
We start with 10 liters of fuel.
We drive to position 10, expending 10 liters of fuel.  We refuel from 0 liters to 60 liters of gas.
Then, we drive from position 10 to position 60 (expending 50 liters of fuel),
and refuel from 10 liters to 50 liters of gas.  We then drive to and reach the target.
We made 2 refueling stops along the way, so we return 2.

Note:

  1. 1 <= target, startFuel, stations[i][1] <= 10^9
  2. 0 <= stations.length <= 500
  3. 0 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target

這道題說有一輛小車,需要向東行駛 target 的距離,路上有許多加油站,每個加油站有兩個信息,一個是距離起點的距離,另一個是可以加的油量,問我們到達 target 位置最少需要加的油量。我們可以從第三個例子來分析,開始時有 10 升油,可以到達第一個加油站,此時花掉了 10 升,但是可以補充 60 升,當前的油可以到達其他所有的加油站,由於已經開了 10 邁,所以到達后面的加油站的距離分別為 10,20,和 50。若我們到最后一個加油站,那離起始位置就有 60 邁了,再加上此加油站提供的 40 升油,直接就可以到達 100 位置,不用再加油了,所以總共只需要加2次油。由此可以看出來其實我們希望到達盡可能遠的加油站的位置,同時最好該加油站中的油也比較多,這樣下一次就能到達更遠的位置。像這種求極值的問題,十有八九要用動態規划 Dynamic Programming 來做,但是這道題的 dp 定義式並不是直接來定義需要的最少加油站的個數,那樣定義的話不太好推導出狀態轉移方程。正確的定義應該是根據加油次數能到達的最遠距離,我們就用一個一維的 dp 數組,其中 dp[i] 表示加了i次油能到達的最遠距離,那么最后只要找第一個i值使得 dp[i] 大於等於 target 即可。dp 數組的大小初始化為加油站的個數加1,值均初始化為 startFuel 即可,因為初始的油量能到達的距離是確定的。現在來推導狀態轉移方程了,遍歷每一個加油站,對於每個遍歷到的加油站k,需要再次遍歷其之前的所有的加油站i,能到達當前加油站k的條件是當前的 dp[i] 值大於等於加油站k距起點的距離,若大於等於的話,我們可以更新 dp[i+1] 為 dp[i]+stations[k][1],這樣就可以得到最遠能到達的距離。當 dp 數組更新完成后,需要再遍歷一遍,找到第一個大於等於 target 的 dp[i] 值,並返回i即可,參見代碼如下:
解法一:
class Solution {
public:
    int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
        int n = stations.size();
        vector<long> dp(n + 1, startFuel);
        for (int k = 0; k < n; ++k) {
            for (int i = k; i >= 0 && dp[i] >= stations[k][0]; --i) {
                dp[i + 1] = max(dp[i + 1], dp[i] + stations[k][1]);
            }
        }
        for (int i = 0; i <= n; ++i) {
            if (dp[i] >= target) return i;
        }
        return -1;
    }
};

這道題還有一個標簽是 Heap,說明還可以用堆來做,這里是用最大堆。因為之前也分析了,我們關心的是在最小的加油次數下能達到的最遠距離,那么每個加油站的油量就是關鍵因素,可以將所有能到達的加油站根據油量的多少放入最大堆,這樣每一次都選擇油量最多的加油站去加油,才能盡可能的到達最遠的地方(如果驕傲沒被現實大海冷冷拍下,又怎會懂得要多努力,才走得到遠方。。。打住打住,要唱起來了 ^o^)。這里需要一個變量i來記錄當前遍歷到的加油站的位置,外層循環的終止條件是 startFuel 小於 target,然后在內部也進行循環,若當前加油站的距離小於等於 startFuel,說明可以到達,則把該加油站油量存入最大堆,這個 while 循環的作用就是把所有當前能到達的加油站的油量都加到最大堆中。這樣取出的堆頂元素就是最大的油量,也是我們下一步需要去的地方(最想要去的地方,怎么能在半路就返航?!),假如此時堆為空,則直接返回 -1,表示無法到達 target。否則就把堆頂元素加到 startFuel 上,此時的startFuel 就表示當前能到的最遠距離,是不是跟上面的 DP 解法核心思想很類似。由於每次只能去一個加油站,此時結果 res 也自增1,當 startFuel 到達 target 時,結果 res 就是最小的加油次數,參見代碼如下:
解法二:
class Solution {
public:
    int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
        int res = 0, i = 0, n = stations.size();
        priority_queue<int> pq;
        for (; startFuel < target; ++res) {
            while (i < n && stations[i][0] <= startFuel) {
                pq.push(stations[i++][1]);
            }
            if (pq.empty()) return -1;
            startFuel += pq.top(); pq.pop();
        }
        return res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/871


參考資料:

https://leetcode.com/problems/minimum-number-of-refueling-stops/

https://leetcode.com/problems/minimum-number-of-refueling-stops/discuss/149839/DP-O(N2)-and-Priority-Queue-O(NlogN)

https://leetcode.com/problems/minimum-number-of-refueling-stops/discuss/149858/Simple-Java-Solution-Using-PriorityQueue-O(nlogn)


[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM