问题描述:
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.
解题思路:
这道题感觉就是个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:
- your HP[i][j]+dungeon[i][j] should be >0
- 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走不通,那就换个思路倒着来一次~