题目
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
-2 (K) | -3 | 3 |
-5 | -10 | 1 |
10 | 30 | -5 (P) |
说明:
- 骑士的健康点数没有上限。
- 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
链接(中文版):https://leetcode-cn.com/problems/dungeon-game/
链接(英文版):https://leetcode.com/problems/dungeon-game/
分析
计算从地图任意点P出发达到右下角所需最少健康点数MIN时,需要知道从P右边出发到达右下角所需最少健康点数MIN_right,以及从P下边出发到达右下角所需最少将康点数MIN_down。从MIN_right和MIN_down中选择较小的,假设是MIN_right,值为2,再假设此时P位置地牢的数值是-3,则从P出发所需最少将康点数MIN=5。即,在P处健康值是5,但是需要减去3,变为2,然后往右走,需要MIN_right=2点健康值才能到达右下角。
根据动态规划算法,MIN_right和MIN_down已经计算得到。当算法结束时,从地牢任意点出发到达右下角所需的最少健康点数都有了。
求解流程
初始化和地牢形状一样的矩阵dp,为简化地牢矩阵边界的判断,我们先计算出下图红色X位置的答案。
X | ||
X | ||
X | X | X |
根据上述分析,得到如下答案。(注意:所需最少健康点数最少为1,如果整个地牢的值都大于等于0,则最终dp内容全是1)。
2 | ||
5 | ||
1 | 1 | 6 |
然后计算dp[1][1],此时下侧dp[2][1]=1,右侧dp[1][2]=5,较小值是1,地牢[1][1]=-10,则dp[1][1]=11。
2 | ||
11 | 5 | |
1 | 1 | 6 |
然后计算dp[1][0],此时下侧dp[2][0]=1,右侧dp[1][1]=11,较小值是1,地牢[1][1]=-5,则dp[1][1]=6。
2 | ||
6 | 11 | 5 |
1 | 1 | 6 |
最终答案如下,返回dp[0][0]。
7 | 5 | 2 |
6 | 11 | 5 |
1 | 1 | 6 |
代码
class Solution:
def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
M, N = len(dungeon), len(dungeon[0])
dp = [[0] * N for _ in range(M)] #初始dp矩阵,和地牢大小一致
dp[-1][-1] = 1 if dungeon[-1][-1] >= 0 else -dungeon[-1][-1] + 1 #如果右下角数值大于等于0,则dp[-1][-1]=1,否则是-dungeon[-1][-1] + 1
for y in range(M-2, -1, -1): #计算最右侧的答案,行坐标范围是M-2到0闭区间
dp[y][-1] = -dungeon[y][-1] + dp[y+1][-1]
if dp[y][-1] < 1:
dp[y][-1] = 1
for x in range(N-2, -1, -1): #计算最下侧的答案,列坐标范围是N-2到0闭区间
dp[-1][x] = -dungeon[-1][x] + dp[-1][x+1]
if dp[-1][x] < 1:
dp[-1][x] = 1
for y in range(M-2, -1, -1): #计算其余位置答案
for x in range(N-2, -1, -1):
tmp = min(dp[y+1][x], dp[y][x+1])
dp[y][x] = -dungeon[y][x] + tmp
if dp[y][x] < 1:
dp[y][x] = 1
return dp[0][0]