演算法作業第六週(leetcode)——174. Dungeon Game
這似乎是我在leetcode上做的第一道動態規劃的題。這道題挺有意思的。下面給出題目地址:
這道題的意思是說給定一個m*n的陣列,陣列中的每一個位置代表地牢中的一個房間,裡面的數代表裡面的怪獸(負數)!血藥(正數),和空房間(零)。王子要從(0,0)位置出發,只能往右或往下走,走到(m-1,n-1)位置,王子會有一個血量,血量小於等於零時王子死亡,現在要找出一條路徑使王子不死且需要的初始生命值最小。簡單來說,這題可以看作尋找一條只能往右或往下的路徑,路徑上每個位置的狀態值等於經過的數之和,找出使最小狀態值最大的路徑。
這咋一看有點像一道圖論題。像是在找最小帶權路。但是這題卻是一道動態規劃題,但是狀態的轉移是由技巧的。如果從(0,0)往後進行動態規劃,那麼我們的目標是要求到終點的路徑的最大最小狀態值,所以要儲存最小狀態值。如果這樣儲存,我們很容易發現要遞推下一個位置上的最大最小狀態值不僅跟過去節點的最大最小狀態值有關,也和當前生命值有關。如果只儲存最大最小狀態值,就是說到終點的使最小狀態值最大的路徑,不一定就是到結果路徑上某個節點的最大最小狀態值路徑相同,即全域性最優解不包含區域性最優解。如果同時儲存生命值,那無疑增加了非常多的計算量。
那麼怎麼辦呢?我想到的方法是從(m-1,n-1)往回動歸 。把這個節點要走到終點需要的最小生命值作為節點的狀態值,有這個狀態值遞推下個節點的狀態值(遞推的時候要保證狀態值大於1)。這樣,如果找到一條路徑從起點以最小生命值走到終點,那麼在這個路徑上的每一個節點都應該大於等於節點的最小生命值並可以走節點狀態值最小的路徑,使全域性最優解包含區域性最優解。
這裡很容易想到的是遞迴,用一個數組儲存節點是否訪問過,不過這裡可以用迴圈。把每一個從右上到左下的斜邊看作一個階段。下一個階段的值由上一個階段的值決定。就可以看作是多階段的動態規劃,可以用迴圈解決。此處用刷表法或填表法都可以。
以下是實現程式碼:
class Solution { public: vector<vector<int>> dp; int calculateMinimumHP(vector<vector<int>>& dungeon) { int i,j; int M = dungeon.size(); int N = dungeon[0].size(); for(i=0;i<M;i++) { vector<int> temp(N); dp.push_back(temp); } dp[M-1][N-1]=-dungeon[M-1][N-1]+1; if(dp[M-1][N-1]<1) dp[M-1][N-1]=1; for(i=M+N-3;i>=0;i--) { for(j=max(0,i-N+1);j<=min(M-1,i);j++) { if(j+1<M) { dp[j][i-j]=dp[j+1][i-j]-dungeon[j][i-j]; if(i-j+1<N) dp[j][i-j]=min(dp[j][i-j+1]-dungeon[j][i-j],dp[j][i-j]); } else if(i-j+1<N) dp[j][i-j]=dp[j][i-j+1]-dungeon[j][i-j]; else cout<<"aaaaaa"<<endl; if(dp[j][i-j]<1) dp[j][i-j]=1; //cout<<j<<" "<<i-j<<" "<<dp[j][i-j]<<endl; } } return dp[0][0]; } };
結果: