1.题目链接:
2.题目描述:
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
-2 (K) -3 3
-5 -10 1
10 30 -5 (P)
3. 解法(动态规划):
算法思路:
1. 状态表示:
这道题如果我们定义成:从起点开始,到达 [i, j] 位置的时候,所需的最低初始健康点数。
那么我们分析状态转移的时候会有一个问题:那就是我们当前的健康点数还会受到后面的路径的影
响。也就是从上往下的状态转移不能很好地解决问题。
这个时候我们要换一种状态表示:从 [i, j] 位置出发,到达终点时所需要的最低初始健康点
数。这样我们在分析状态转移的时候,后续的最佳状态就已经知晓。
综上所述,定义状态表示为:
dp[i][j] 表示:从 [i, j] 位置出发,到达终点时所需的最低初始健康点数。
2. 状态转移方程:
对于 dp[i][j] ,从 [i, j] 位置出发,下一步会有两种选择(为了方便理解,设 dp[i] [j] 的最终答案是 x ):
i. | 走到右边,然后走向终点 |
那么我们在 [i, j] 位置的最低健康点数加上这一个位置的消耗,应该要大于等于右边位置的最低健康点数,也就是:x + dungeon[i][j] >= dp[i][j + 1] 。
通过移项可得:x >= dp[i][j + 1] - dungeon[i][j] 。因为我们要的是最小值,因此这种情况下的x = dp[i][j + 1] - dungeon[i][j] ;
ii. 走到下边,然后走向终点
那么我们在 [i, j] 位置的最低健康点数加上这一个位置的消耗,应该要大于等于下边位置的最低健康点数,也就是:x + dungeon[i][j] >= dp[i + 1][j] 。
通过移项可得:x >= dp[i + 1][j] - dungeon[i][j] 。因为我们要的是最小值,因此这种情况下的x = dp[i + 1][j] - dungeon[i][j] ;
综上所述,我们需要的是两种情况下的最小值,因此可得状态转移方程为:
dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j] |
但是,如果当前位置的 dungeon[i][j] 是一个比较大的正数的话,dp[i][j] 的值可能变成 0 或者负数。也就是最低点数会小于 1 ,那么骑士就会死亡。因此我们求出来的 dp[i] [j] 如果小于等于 0 的话,说明此时的最低初始值应该为 1 。处理这种情况仅需让 dp[i] [j] 与 1 取一个最大值即可:
dp[i][j] = max(1, dp[i][j]) |
3. 初始化:
可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
i. 辅助结点里面的值要「保证后续填表是正确的」;
ii. 「下标的映射关系」。
在本题中,在 dp 表最后面添加一行,并且添加一列后,所有的值都先初始化为无穷大,然后让 dp[m][n - 1] = dp[m - 1][n] = 1 即可。
4. 填表顺序:
根据「状态转移方程」,我们需要「从下往上填每一行」,「每一行从右往左」。
5. 返回值:
根据「状态表示」,我们需要返回 dp[0][0] 的值。
Java算法代码:
class Solution {
public int calculateMinimumHP(int[][] d) {
// 1.创建dp表
// 2.初始化
// 3.填表
// 4.返回值
int m = d.length, n = d[0].length;
int [][] dp = new int[m+1][n+1];
for(int j = 0; j <= n; j++) dp[m][j] = Integer.MAX_VALUE;
for(int i = 0; i <= m; i++) dp[i][n] = 0x3f3f3f;
dp[m][n-1] = dp[m-1][n]=1;
for(int i = m -1; i >=0; i--)
for(int j = n -1; j>=0; j--){
dp[i][j] = Math.min(dp[i][j + 1],dp[i+1][j]) - d[i][j];
dp[i][j] = Math.max(dp[i][j],1);
}
return dp[0][0];
}
}
运行结果:
动态规划: