Leetcode—174. Dungeon Game 倒序DP

問題描述:

The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned in the top-left room and must fight his way through the dungeon to rescue the princess.

The knight has an initial health point represented by a positive integer. If at any point his health point drops to 0 or below, he dies immediately.

Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering these rooms; other rooms are either empty (0's) or contain magic orbs that increase the knight's health (positive integers).

In order to reach the princess as quickly as possible, the knight decides to move only rightward or downward in each step.


Write a function to determine the knight's minimum initial health so that he is able to rescue the princess.

For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path RIGHT-> RIGHT -> DOWN -> DOWN.

-2 (K) -3 3
-5 -10 1
10 30 -5 (P)

Notes:

  • The knight's health has no upper bound.
  • Any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned.
終於有道遊戲的問題,激動激動~ 這道題講的是類似於魔塔的問題,有一個初始血量,走到正的格子就加血,負的格子就減血,血量不能小於等於0,問從左上角走到有下角,最少的初始血量應該設置爲多少?


解題思路:

這道題感覺就是個DP呀,來來來,讓我們從左上角走一遭~走着走着我們就會發現問題好像並不是這麼簡單,如果從左上到右下我們選擇最小的血量,顯而易見的,hp[ i ][ j ] 最優解應該拆分成 hp[ i-1 ][ j ] 和 hp[ i ][ j -1] 的子問題最優解。但是從兩個位置如何選擇作爲當前位置的最優解?需要保證整個路徑上的最小血量最大?還是保證到當前位置的血量最大?好像是個無法選擇的問題。比如

hp[ i-1 ][ j ] : 0/-9  hp[ i ][ j-1 ] :10 /-10  第一個值代表當前血量,第二隻代表到這個點的路徑的最低血量(假設初始爲0)。我們要如何選擇呢?如果選了0/-9, 因爲他有較大的最低血量,但是如果hp[ i ][ j+1 ] = -20 怎麼辦? 如果選擇10/-10,如果後面的點都是回血的,該怎麼辦,所以好像沒法選擇……

我在Leetcode的discuss裏面找到了一個解釋,爲什麼從左上到右下是不行的:

This is because, in order to compute HP[i][j], you will need to make sure of two things:

  1. your HP[i][j]+dungeon[i][j] should be >0
  2. your HP[i][j]+dungeon[i][j] should be large enough, so that it will be sufficient to cover the minimum requirement on HP for the next step, be it right or down (take whichever requires smaller HP).

So you see, because of the second requirement, your calculation of HP[i][j] will depend on later steps that you could take. This is why you have to know these later steps ahead of time, thus calculating from the bottom right.

看完應該大概理解了,因爲兩個選擇條件,並且後面位置對當前位置最優選擇會產生影響,我們知道DP子問題的選擇和當前問題是相對獨立的,不受後面步驟的影響,因此這是不適合用DP的。看到這裏是不是很沮喪,但是有沒有解決方法呢,有!那就是順序!!

如果從前到後,後面的點會影響前面的點,那我們能不能倒着來,讓後面的到前面!這道題是可以從右下方到左上方是可以使用DP的!

Use hp[i][j] to store the min hp needed at position (i, j), then do the calculation from right-bottom to left-up.

Note: adding dummy row and column would make the code cleaner.

class Solution {
public:
    int calculateMinimumHP(vector<vector<int> > &dungeon) {
        int M = dungeon.size();
        int N = dungeon[0].size();
        // hp[i][j] represents the min hp needed at position (i, j)
        vector<vector<int> > hp(M + 1, vector<int>(N + 1, INT_MAX));
        hp[M][N - 1] = 1;
        hp[M - 1][N] = 1;
        
        //如果是從左上角開始找,需要同時考慮當前血量和路徑最低血量,是真的難....
        //正着dp不行,就嘗試下倒着dp
        for (int i = M - 1; i >= 0; i--) {
            for (int j = N - 1; j >= 0; j--) {
                int need = min(hp[i + 1][j], hp[i][j + 1]) - dungeon[i][j];
                //關鍵的一步,保證了其血量的正值
                hp[i][j] = need <= 0 ? 1 : need;
            }
        }
        return hp[0][0];
    }
};

注意如果倒着來,問題就變成了從當前位置到右下角需要的最低血量是多少,也是可以拆分子問題的形式。最主要的一點在於hp[i][j] = need <= 0 ? 1 : need; 

如果當前位置的血量是正值,如果當前位置到終點不會扣血,(即使負血也能過),我們就讓這個位置爲1。調轉一下思路,你就明白了~


如果正向DP走不通,那就換個思路倒着來一次~



發佈了40 篇原創文章 · 獲贊 63 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章