赞
踩
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon){
}
};
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) 有多条路径,我们取其中最有代表性的两条:
我们希望[从出发点到当前点的路径和]尽可能大,而[从出发点到当前点所需的最小初始值]尽可能小。这两条路径各有优缺点。
在上图中,我们知道应该选取绿色路径,因为蓝色路径的路径和太小,使得蓝色路径需要增大初始值到 4才能走到终点,而绿色路径只要 3 点初始值就可以直接走到终点。但是如果我们把终点的-2换成0,蓝色路径只需要初始值2,蓝色路径仍需要初始值3,最优决策就变成蓝色路径了。
因此,如果我们按照从左上到右下的顺序进行动态规划,我们就无法直接确定到达 (1,2)的方案,因为有两个重要程度相同的参数同时影响后继的决策。这就是说,这样的动态规划无法满足[无后效性]的。
于是我们考虑从右下往左上进行动态规划。
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 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。