在上一篇博客中,我们了解两个关于路径的问题,在这一篇中,我们将进一步了解一道更加困难的问题
题目描述
恶魔们抓住了公主并将她关在了地下城的右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。 为了尽快解救公主,骑士决定每次只 向右 或 向下 移动一步。 返回确保骑士能够拯救到公主所需的最低初始健康点数。
注意:任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
解题思路
在这个问题中,我们定义dp[i][j]
为骑士从网格中的(i, j)
位置(即第i
行第j
列的房间)出发,到达终点(右下角)所需的最低初始健康点数。
初始化
-
首先,我们需要考虑边界情况。由于骑士必须从左上角出发,因此
dp[0][0]
应该根据dungeon[0][0]
的值进行调整。如果dungeon[0][0]
是负的,那么骑士至少需要-dungeon[0][0] + 1
的健康点数来确保从起点出发时不会立即死亡。如果dungeon[0][0]
是非负的,那么骑士的初始健康点数可以设为1(因为已经足够应对该房间的情况)。 -
对于其他边界位置(第一行和第一列),我们需要确保骑士在到达这些位置时不会死亡,并计算到达这些位置所需的最低健康点数。这通常涉及向前看(向右或向下),以确定从当前位置到边界的下一个安全点所需的健康点数。
状态转移方程
对于网格中的非边界位置(i, j)
,我们可以根据以下规则计算dp[i][j]
:
-
如果骑士从
(i, j)
向右移动到(i, j+1)
是安全的(即dp[i][j+1]
存在且不会导致死亡),则dp[i][j]
可以是min(1, dp[i][j+1] - dungeon[i][j])
。这里的1
表示骑士至少需要1点健康点数才能存活,而dp[i][j+1] - dungeon[i][j]
表示从(i, j)
向右移动后到达(i, j+1)
时所需的健康点数。 -
如果骑士从
(i, j)
向下移动到(i+1, j)
是安全的,则类似地,dp[i][j]
可以是min(1, dp[i+1][j] - dungeon[i][j])
。 -
但是,骑士不能同时向右和向下移动,因此我们需要取上述两种情况中的较大值,以确保无论选择哪个方向,骑士都有足够的健康点数。即:
dp[i][j]=max(1,min(dp[i][j+1]−dungeon[i][j],dp[i+1][j]−dungeon[i][j]))
注意,如果
dungeon[i][j]
是一个很大的正数,那么骑士在到达(i, j)
后可能会拥有超过dp[i][j+1]
或dp[i+1][j]
的健康点数。然而,我们关心的是确保骑士在到达(i, j)
时至少拥有1
点健康点数,并且有足够的健康点数来应对接下来的移动。
特殊情况处理
- 当骑士接近右下角时,他/她需要确保有足够的健康点数来应对最后一个房间(即公主所在的房间)。因此,在计算
dp[m-1][n-1]
时,我们需要确保dp[m-1][n-1] - dungeon[m-1][n-1] >= 1
,即使这意味着dp[m-1][n-1]
需要被设置为一个相对较大的值。
通过这种方法,我们可以从右下角开始,逆向填充dp
数组,直到左上角。最终,dp[0][0]
将包含骑士从左上角出发,确保能够拯救公主所需的最低初始健康点数。
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon) {
int m = dungeon.size();
int n = dungeon[0].size();
// 创建一个DP数组,初始化为0
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[m - 1][n - 1] = max(1, 1 - dungeon[m - 1][n - 1]); // 右下角特殊处理
// 初始化最后一列
for (int i = m - 2; i >= 0; i--) {
dp[i][n - 1] = max(1, dp[i + 1][n - 1] - dungeon[i][n - 1]);
}
// 初始化最后一行
for (int j = n - 2; j >= 0; j--) {
dp[m - 1][j] = max(1, dp[m - 1][j + 1] - dungeon[m - 1][j]);
}
// 填充DP数组的其他部分
for (int i = m - 2; i >= 0; i--) {
for (int j = n - 2; j >= 0; j--) {
dp[i][j] =
max(1, min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j]);
}
}
return dp[0][0];
}
};
代码解释
-
我们首先定义了一个
dungeon
的二维向量来表示地下城,并初始化了DP数组dp
。 -
我们从右下角开始逆向计算DP数组。因为右下角是终点,所以我们可以直接根据
dungeon[m-1][n-1]
的值来设置dp[m-1][n-1]
。 -
然后,我们分别初始化DP数组的最后一列和最后一行。这是因为当我们处于这些边界上时,我们只能沿着一个方向移动(向右或向下),所以计算相对简单。
-
接下来,我们填充DP数组的其他部分。对于每个位置
(i, j)
,我们查看其右侧和下方的位置,并选择其中所需健康点数较小的那个。但是,我们还需要确保这个值至少为1,因为骑士至少需要1点健康点数才能存活。 -
最后,我们返回
dp[0][0]
,即从左上角出发所需的最低初始健康点数。