文章目录
- 1题目理解
- 2 分析
1题目理解
故事背景:恶魔把公主抓走了,关在地牢里面。骑士想要把公主救出来。初始化的时候,骑士有一个健康值init。
输入:int[][] dungeon表示地牢中魔鬼布局图。dungeon[i][j]>0,恶魔会提高骑士的健康值dungeon[i][j];dungeon[i][j]会降低 骑士健康值(掉血)。如果骑士健康值为0,那骑士就牺牲了。
输出:骑士需要的最小健康值。
例如:
输入:
-2 (K) | -3 | 3 |
---|---|---|
-5 | -10 | 1 |
10 | 30 | -5(P) |
输出:7
按照 右->右->下->下的路线,健康值最小是7即可。
2 分析
解题思路来源于力扣。
从左上角走到右下角有很多种走法,参考63题。找到每一种需要的最小健康值,求最小值就是答案。对于这种比较难的题目,会先列出解题框架然后再分析填充。
先写暴力搜索的框架。将与状态无关的变量:dungeon,m,n等放到实例变量中。从(0,0)开始走,横纵坐标是状态相关的,作为方法参数。
class Solution {private int[][] dungeon;private int m;private int n;private int minHealth;public int calculateMinimumHP(int[][] dungeon) {this.dungeon = dungeon;m = dungeon.length;n = dungeon[0].length;minHealth = Integer.MAX_VALUE;visit(0,0);return minHealth;}private void visit(int i, int j){if(i>=m || j>=n){....return;}...}
}
需要保证骑士在到达(i,j)之后健康值大于0,那就需要上一步的健康值。
从(i,j)可以达到(i+1,j)、(i,j+1)。
class Solution {...public int calculateMinimumHP(int[][] dungeon) {this.dungeon = dungeon;m = dungeon.length;n = dungeon[0].length;minHealth = Integer.MAX_VALUE;visit(0,0,0);return minHealth;}private void visit(int i, int j, int preHealth){if(i>=m || j>=n){return;}int health = preHealth+ dungeon[i][j];...}
}
health如果小于1,那么就需要abs(health)+1的初始健康值。
health如果大于 1,那么就初始健康值可以为0。
也就是 init=0 if health>=1;init=abs(health)+1,if health<=0
找到了当前状态的init,还需要查找到这条路径上最大的init才是就出公主的,当前路径上需要的最小的init。例如-2->-3->3->1->-5这条路径。在(0,1)这一点health=6,那么init=0。在(0,2),init = 3。需要找到这条路径上的最大值,才能保证走这条路完成任务。
class Solution {...private void visit(int i, int j, int preHealth,int maxInitOfPath){if(i>=m || j>=n){return;}preHealth += dungeon[i][j];int init = preHealth>=1?0:Math.abs(preHealth)+1;maxInit = Math.max(maxInit,init);}
}
接下来分析完成的标志是到达(m,n-1),或者(m-1,n)。达到(m-1,n-1)之后,再走一步,确定全局最小init。
public int calculateMinimumHP(int[][] dungeon) {this.dungeon = dungeon;m = dungeon.length;n = dungeon[0].length;minHealth = Integer.MAX_VALUE;visit(0,0,0,0);return minHealth;}private void visit(int i, int j, int preHealth,int maxInitOfPath){if(i==m-1 && j==n || i==m && j==n-1){minHealth = Math.min(minHealth,maxInitOfPath);return;}if(i>=m || j>=n){return;}preHealth += dungeon[i][j];int init = preHealth>=1?0:Math.abs(preHealth)+1;maxInitOfPath = Math.max(maxInitOfPath,init);visit(i+1,j,preHealth,maxInitOfPath);visit(i,j+1,preHealth,maxInitOfPath);}
minHealth就是最小需要的初始健康值。
再进一步观察发现preHealth其实就是路径上dungeon[i][j]的和。maxInitOfPath的取值和整条路径中的的最小值有关系。也就是说路径和、路径上的最小值,与最终结果有关系。动态规划只能表示一个变量,没有办法把这两个因素都放入转移方程。
如果从右下角向左上角进行递归,我们需要保证达到(i,j)的时候的路径和preSum>dungeon[i][j],即可。
令dp[i][j] 表示从坐标 (i,j)(i,j) 到终点所需的最小初始值,dp[i][j]与dp[i+1][j]、dp[i][j+1]有关系 。
dp[i][j]=min(dp[i+1][j],dp[i][j+1])−dungeon[i][j]dp[i][j] = min(dp[i+1][j],dp[i][j+1]) - dungeon[i][j]dp[i][j]=min(dp[i+1][j],dp[i][j+1])−dungeon[i][j]。
考虑初始化:dp[m-1][n] = dp[m][n-1]=1。
class Solution {public int calculateMinimumHP(int[][] dungeon) {int m = dungeon.length;int n = dungeon[0].length;int[][] dp = new int[m+1][n+1];for(int[] d : dp){Arrays.fill(d,Integer.MAX_VALUE);}dp[m][n-1]=dp[m-1][n]=1;for(int i=m-1;i>=0;i--){for(int j=n-1;j>=0;j--){int min = Math.min(dp[i+1][j], dp[i][j+1]);dp[i][j] = Math.max(min-dungeon[i][j],1);}}return dp[0][0];}}