题目

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快到达公主,骑士决定每次只向右或向下移动一步。

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

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

说明:

骑士的健康点数没有上限。

任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/dungeon-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

从右下角网左上角一次填充,174. 地下城游戏 - 图1表示能够到达右下角的最小体力值。

数组初始化时先填最下面一行和最右面一列。然后剩余位置都依赖于右边和下边的值,状态转移的核心逻辑是,如果当前可以获得的体力小于相邻处要求的最低体力值,那么这个差值就是当前位置可能的最小体力值,反之如果大于,那么该处只需要最低的体力值1就好了。

代码

  1. class Solution {
  2. public int calculateMinimumHP(int[][] dungeon) {
  3. int m = dungeon.length;
  4. int n = dungeon[0].length;
  5. int[][] dp = new int[m][n];
  6. // 右下角如果为正,达到该处最小体力值为最小值1;为负则需在消耗这个体力后还存留一个体力,因此为 -dungeon[m - 1][n - 1] + 1
  7. dp[m - 1][n - 1] = dungeon[m - 1][n - 1] < 0 ? -dungeon[m - 1][n - 1] + 1 : 1;
  8. for (int i = m - 2; i >= 0; i--) {
  9. dp[i][n - 1] = Math.max(dp[i + 1][n - 1] - dungeon[i][n - 1], 1);
  10. }
  11. for (int i = n - 2; i >= 0; i--) {
  12. dp[m - 1][i] = Math.max(dp[m - 1][i + 1] - dungeon[m - 1][i], 1);
  13. }
  14. for (int i = m - 2; i >= 0; i--) {
  15. for (int j = n - 2; j >= 0; j--) {
  16. dp[i][j] = Math.max(Math.min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j], 1);
  17. }
  18. }
  19. return dp[0][0];
  20. }
  21. }

矩阵两个维度长度各加1,逻辑可以稍微统一点,不用单独初始化最下面和最右边。

  1. class Solution {
  2. public int calculateMinimumHP(int[][] dungeon) {
  3. int m = dungeon.length;
  4. int n = dungeon[0].length;
  5. int[][] dp = new int[m + 1][n + 1];
  6. // 初始化最大值表示不能通过边界
  7. for (int i = 0; i < m + 1; i++) {
  8. dp[i][n] = Integer.MAX_VALUE;
  9. }
  10. for (int i = 0; i < n + 1; i++) {
  11. dp[m][i] = Integer.MAX_VALUE;
  12. }
  13. // 通过上面初始化dp[m-1][n-1]也可以看出,-dungeon[m - 1][n - 1] + 1 其实也可以写做 1 - dungeon[m - 1][n - 1]
  14. // 表示达到右边和下面的最低体力要求为1,确实是这样,因为达到终点了
  15. dp[m][n - 1] = 1;
  16. dp[m - 1][n] = 1;
  17. for (int i = m - 1; i >= 0; i--) {
  18. for (int j = n - 1; j >= 0; j--) {
  19. dp[i][j] = Math.max(Math.min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j], 1);
  20. }
  21. }
  22. return dp[0][0];
  23. }
  24. }