【動態規劃】——最低加油次數
871. 最低加油次數 - 力扣(LeetCode) (leetcode-cn.com)
汽車從起點出發駛向目的地,該目的地位於出發位置東面 target 英里處。
沿途有加油站,每個 station[i] 代表一個加油站,它位於出發位置東面 station[i][0] 英里處,並且有 station[i][1] 升汽油。
假設汽車油箱的容量是無限的,其中最初有 startFuel 升燃料。它每行駛 1 英里就會用掉 1 升汽油。
當汽車到達加油站時,它可能停下來加油,將所有汽油從加油站轉移到汽車中。
為了到達目的地,汽車所必要的最低加油次數是多少?如果無法到達目的地,則返回 -1 。
注意:如果汽車到達加油站時剩餘燃料為 0,它仍然可以在那裡加油。如果汽車到達目的地時剩餘燃料為 0,仍然認為它已經到達目的地。
分析:這道題一開始我想了很久,單純考慮在某一個加油站加油後能否撐到下一個加油站,又或者是否能撐到終點,結果發現情況很複雜,程式碼越加越長,卻依然過不了。於是只能去學習大佬解法。不得不說,解決動態規劃問題設定狀態真是一門技術活兒,狀態量設定得巧妙題目難度就會直線下降。
設dp[i][j]表示經過i個加油站,加了j次油後車輛能行駛的最大距離。那麼,按照題目要求,我們只需要找出經過n個加油站後能夠到達目的地的最少加油次數j。即dp[n][j]>=target的最小j值。對於途徑的加油站i,我們都有兩個選擇:加油或不加油,如果我們在第i站選擇不加油,那麼我們經過第i個加油站後能行駛的最大距離dp[i][j]=dp[i-1][j]。如果我們選擇在第i個加油站加油,那麼我們能行駛的最大距離dp[i][j]=dp[i-1][j-1]+stations[i][1]。以上兩種情況成立的前提是我們上一狀態的最大距離能夠撐到第i個加油站。
class Solution { public: int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) { int n=stations.size(); if(n==0){ if(startFuel>=target) return 0; else return -1; } vector<vector<long long>> dp(n+1,vector<long long>(n+1,0)); //將起點記作第零個加油站,便於處理。 //dp[i][j]:經過i個加油站前一共加了j次油能跑的最遠距離 for(int i=0;i<=n;i++){ dp[i][0]=startFuel; //如果一次油都不加,那麼無論經過多少個加油站最遠距離都只有startFuel。 } for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ if(dp[i-1][j]>=stations[i-1][0]){ //第i站不加油 dp[i][j]=dp[i-1][j]; } if(dp[i-1][j-1]>=stations[i-1][0]){ //第i站加油 dp[i][j]=max(dp[i][j],dp[i-1][j-1]+stations[i-1][1]); } } } for(int j=0;j<=n;j++){ if(dp[n][j]>=target){ return j; } } return -1; } };
空間優化:觀察到上述的狀態轉換式,不難看出當前狀態dp[i][j]只與i-1時的狀態有關,因此我們可以降維:將i維度隱去,只考慮dp[j]:加j次油後能夠跑的最遠距離。這裡關鍵點是內層迴圈的遍歷順序:逆序遍歷。這是因為dp[i][j]只與dp[i-1][j-1]相關。我們在更新狀態時,需要用到第i-1個狀態時的最遠距離。舉個例子,使用dp[0]和dp[1]來代替dp[1][0]和dp[1][1],現在我們想用新的dp[1]和dp[2]來代替原來的dp[2][1]和dp[2][2]。根據上面的二維的情況,dp[2][2]的更新需要用到的是dp[1][1]的情況,我們如果正序遍歷,那麼dp[1]就會先被更新為[2][1]的狀態,顯然就會發生錯誤,所以我們就需要從後往前進行狀態轉移。
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
int n=stations.size();
if(n==0){
if(startFuel>=target) return 0;
else return -1;
}
vector<long long> dp(n+1,0);
dp[0]=startFuel;
for(int i=1;i<=n;i++){
for(int j=i;j>=1;--j){
if(dp[j-1]>=stations[i-1][0]){
dp[j]=max(dp[j],dp[j-1]+stations[i-1][1]);
}
}
}
for(int j=0;j<=n;j++){
if(dp[j]>=target){
return j;
}
}
return -1;
}
};
最後,我們其實不難看出這題是一道0/1揹包問題(最簡單的揹包問題——0/1揹包問題 - 天涯海角尋天涯 - 部落格園 (cnblogs.com))的變式題,所以對於一些基本的模型問題我們一定要滾瓜爛熟,靈活應用。