174. Dungeon Game

文章目录

1题目理解

故事背景:恶魔把公主抓走了,关在地牢里面。骑士想要把公主救出来。初始化的时候,骑士有一个健康值init。
输入:int[][] dungeon表示地牢中魔鬼布局图。dungeon[i][j]>0,恶魔会提高骑士的健康值dungeon[i][j];dungeon[i][j]会降低 骑士健康值(掉血)。如果骑士健康值为0,那骑士就牺牲了。
输出:骑士需要的最小健康值。
例如:
输入:

-2 (K)-33
-5-101
1030-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]有关系 。
d p [ i ] [ j ] = m i n ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) − d u n g e o n [ 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];
         
    }
   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值