leetcode:174. 地下城游戏

题目来源

题目描述

在这里插入图片描述

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon){
    
    }
};

题目解析

DFS

  • 题目求骑士出发时所需要的最小生存血量,它走过一个点就会加上该点的权重(有正有负)
  • 每次有两种选择,向下,向右
  • 可以用递归够:
    • “嗨,dfs函数,帮我算出:如果我走右边的点,我至少要带多少安全血量”
    • “嗨,dfs函数,帮我算出:如果我走下边的点,我至少要带多少安全血量”
    • “我根据上面的返回值,算出:走到我当前的点,我至少要带的安全血量”
class Solution {
    //  返回:来到点(i,j)至少要带多少血量
    int minSaveDp(vector<vector<int>>& dungeon, int i, int j){
        int m = dungeon.size(), n = dungeon[0].size();
        
        if(i == m - 1 && j == n - 1){
            // 当来到公主坐标,如果它的权重为正,以1的血量来到这里即可
            // 如果它的权重为负,则要带着 1-dungeon[i][j] 的血量来这
            return dungeon[i][j] > 0 ? 1 : 1 - dungeon[i][j];
        }
        
        int goDown = INT32_MAX, goRight = INT32_MAX;
        // 走下方的点,需要带着的最小安全血量
        if(i < m - 1){
            goDown = minSaveDp(dungeon, i + 1, j);
        }
        // 走右方的点,需要带着的最小安全血量
        if(j < n - 1){
            goRight = minSaveDp(dungeon, i, j + 1); 
        }
        // 如果走下方,所需要带的血量更少
        if(goDown < goRight){
            if (goDown - dungeon[i][j] <= 0) {
                // goDown血量,是来到(i+1,j)的稳妥血量,等于,来到(i,j)的稳妥血量 + dungeon[i][j]
                // 则,goDown血量-dungeon[i][j],就是来到(i,j)点的稳妥血量,如果 <= 0
                // 则要它返回 1,即来到(i,j)点的稳妥血量至少要为 1
                return 1;
            } else {
                // goDown血量,是来到(i+1,j)的稳妥血量,等于,来到(i,j)的稳妥血量 + dungeon[i][j]
                // 则,goDown血量-dungeon[i][j],就是来到(i,j)点的稳妥血量,如果 > 0
                // 则它是安全的,返回它本身即可,即 goDown血量 - dungeon[i][j]
                return goDown - dungeon[i][j];
            }
        }else{
            if (goRight - dungeon[i][j] <= 0) {
                return 1;
            } else {
                return goRight - dungeon[i][j];
            }
        }
    }
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon){
        return minSaveDp(dungeon, 0, 0);  // 至少需要带着多少血量来到(0,0)这点
    }
};

动态规划

分析

  • 如果按照从左上往右下的顺序进行动态规划,对于每一条路径,我们需要同时记录两个值:
    • 第一个是【从出发点到当前点的路径和】
    • 第二个是【从出发点到当前点需要的最小初始值】
  • 这两个值的重要程度相同。

举个例子:
在这里插入图片描述
从 (0,0)到 (1,2) 有多条路径,我们取其中最有代表性的两条:
在这里插入图片描述

  • 绿色路径[从出发点到当前点的路径和]为1,[从出发点到当前点所需的最小初始值]为3
  • 蓝色路径[从出发点到当前点的路径和]为-1,[从出发点到当前点所需的最小初始值]为2

我们希望[从出发点到当前点的路径和]尽可能大,而[从出发点到当前点所需的最小初始值]尽可能小。这两条路径各有优缺点。

在上图中,我们知道应该选取绿色路径,因为蓝色路径的路径和太小,使得蓝色路径需要增大初始值到 4才能走到终点,而绿色路径只要 3 点初始值就可以直接走到终点。但是如果我们把终点的-2换成0,蓝色路径只需要初始值2,蓝色路径仍需要初始值3,最优决策就变成蓝色路径了。

因此,如果我们按照从左上到右下的顺序进行动态规划,我们就无法直接确定到达 (1,2)的方案,因为有两个重要程度相同的参数同时影响后继的决策。这就是说,这样的动态规划无法满足[无后效性]的。

于是我们考虑从右下往左上进行动态规划。

  • 令dp[i][j]表示从坐标[i,j]到达终点所需要的最小初始血量。
  • 从底往上走,[i,j]与[i+1,j]和[i,j+1]有关,这两个点,哪个点需要的初始值小,从[i,j]出发就选择哪一个,当前位置的值是dungeon[i][j],需要减去这个值,也就是min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j],但是每一步的体力值至少为1。因此: d p [ i ] [ j ] = m a x ( m i n ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) − d u n g e o n [ i ] [ j ] , 1 ) dp[i][j] = max(min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j], 1) dp[i][j]=max(min(dp[i+1][j],dp[i][j+1])dungeon[i][j],1)
  • 返回dp[0][0]即答案
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(), n = dungeon[0].size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
        dp[m][n - 1] = 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] = max(1, min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]);
            }
        }
        return dp[0][0];
    }
};

我们可以对空间进行优化,使用一个一维的 dp 数组,并且不停的覆盖原有的值,参见代码如下:

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(), n = dungeon[0].size();
        vector<int> dp(n + 1, INT_MAX);
        dp[n - 1] = 1;
        for (int i = m - 1; i >= 0; --i) {
            for (int j = n - 1; j >= 0; --j) {
                dp[j] = max(1, min(dp[j], dp[j + 1]) - dungeon[i][j]);
            }
        }
        return dp[0];
    }
};

类似题目

题目思路
leetcode:62. 走到右下角有多少条路径 Unique Paths 机器人从[i, j]走到[m, n],一共有几种方法:先判断是不是到了目的地,如果是,那么找到了一种方法;判断是不是只能往右走;判断是不是只能往下走;否则往下+往右(之所以不检查边界是因为已经确保不超过边界))
leetcode:63. 走到右下角有多少条不同路径(可能有障碍) Unique Paths II机器人从[i, j]走到[m, n],一共有几种方法:先边界/障碍物检查,如果是,返回0;再终点检查,如果是,返回1;否则往下+往右
leetcode:64. 走到右下角最小路径和 Minimum Path Sum从终点往前面看,定义从左上角到坐标(i, j)的最短路径和:如果当前是起点,那么直接返回grid[0][0];否则判断是不是第一行,第一行只能从左边走过来 + grid[i][j];再判断是不是第一列,第一列只能从上面走下来 + grid[i][j];普通情况是:取从上面走下来和从左边走过来的最小值+当前坐标的值grid[i][j]
leetcode:174. 骑士救公主需要的最小血量 Dungeon Game逆序动态规划: 令dp[i][j]表示从坐标[i,j]到达终点所需要的最小初始血量。
leetcode:120. 三角形最小路径和
leetcode:980. 从起点到终点一共有多少条路径(有障碍,四个方向可以走,只能走一次,所有的可用格子要用完) III因为四个方向都可以走,所以必须回溯
leetcode:741. 最多能摘多少樱桃樱桃 Cherry Pickup 一个人来回走等价成两个人从起点走到终点。也就是两个人A、B都从左下角走到右下角,都只能向下或者向右走,但是A和B能做出不同的选择。如果,某一时刻,AB进入相同的格子,A和B就只能获得一份。如果某一个位置,A也来过,B也来过,AB一定是同时来的,而不会分先后,因为AB同时走
1463. 摘樱桃 II
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值