【leetcode】174. 地下城游戏

文章讲述了如何利用动态规划方法解决一个关于骑士穿越地下城救出公主的问题,计算从左上角到右下角所需的最低初始健康点数,考虑到沿途可能遇到的增减血量情况。
摘要由CSDN通过智能技术生成

一、题目

恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快解救公主,骑士决定每次只 向右 或 向下 移动一步。
返回确保骑士能够拯救到公主所需的最低初始健康点数。
注意:任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
image.png

二、解法(动态规划):

首先分析这题会很像力扣64. 最小路径和这题 那么就会想假设dp[i][j]的含义就是从起点走到(i,j)这个格子所需的最少血量。我把沿路的血量加起来 求一个这个路上所花的最小血量不就行了?但是这样考虑不到途中王子血量掉到小于等于0时他会死掉
所以当走到(i,j)格子的时候 不仅要考虑最小初始化血量,还要
考虑当前王子的血量值
。这两个考虑的因素会相互制约 那么就没办法推出当前格子的最小初始化血量
因此把这题逆向思考 路线是从公主出发走到王子的格子(右下角走到左上角)推出每个格子到终点的最小血量(后面路线都满足了 那么前面的路线就一定可以满足 这样就不需要思考王子的生命值了)
根据题意是一个二维的dp问题,那么我们可以设置一个二维的dp数组,那么dp[i][j]的含义就是从**(i,j)这个格子走到终点(右下角)所需的最少血量。**

2.1 初始化

那么初始化 因为是右下角走到左上角 肯定先初始化公主格子
那么为了到达公主格子不死掉 是不是我到达时候的血量只要满足这个格子的要求就行了
如果公主格子是血包 那么我只需要维持我的生命体征1就好 如果公主格子是怪兽 那么我所需的血量是不是就是1+打怪兽的血量

if(dungeon[m-1][n-1]>0){
    dp[m-1][n-1]=1;
}else{
    dp[m-1][n-1]=1-dungeon[m-1][n-1];
}

那么最后一行的格子只和其右边的格子有关 最后一列的格子只和其下面的格子有关 (因为我从最后一行的格子或者最后一列的格子到达终点只用走右边或者左边)
**为什么是减?**因为如果是血包dungeon[m-1][j]这个值就为正的 那么你从(m-1,j)走到(m-1,j+1)是不是就可以补充dungeon[m-1][j]这么多血量 那么(m-1,j)格子所需的初始血量就要变少【比如说你从上海开车到北京需要100ml油 其中有一个加油站可以冲50ml油 那么你从上海出发的时候就只需要50ml油就好】
**为什么要和1取最大值?**是因为如果你补充的血量超过了你在这个路上的消耗的话 是不是相当于你最少只要1滴血就好了 反正你被打得快死了都会有血包救你

for(int j=n-2;j>=0;j--){
            dp[m-1][j]=Math.max(dp[m-1][j+1]-dungeon[m-1][j],1);
        }
        for(int i=m-2;i>=0;i--){
            dp[i][n-1]=Math.max(dp[i+1][n-1]-dungeon[i][n-1],1);
        }

2.2 中间状态

dp[i][j]=max{min{dp[i+1][j],dp[i][j+1]}-dungeon[i][j]},1}
这里主要是解释为什么要取最小 因为(i,j)格子受影响的状态有两个格子(因为他下一步不是往下走就是往右走)其他的在初始化最后一行和最后一列的格子时候讲的非常清楚了

2.3 最后结果 dp[0][0] 从起点走到终点所需要的最小血量

三、代码

class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        int m=dungeon.length;
        int n=dungeon[0].length;
        // dp[i][j]表示从ij格子到终点所需的最小血量
        int[][] dp=new int[m][n];
        // 初始化 最后一行和最后一列的格子
        // 公主格子只和公主房间状态有关
        if(dungeon[m-1][n-1]>0){
            dp[m-1][n-1]=1;
        }else{
            dp[m-1][n-1]=1-dungeon[m-1][n-1];
        }
        // 最后一行的格子只和它右边的格子有关 最后一列的格子只和他上面的格子有关
        for(int j=n-2;j>=0;j--){
            dp[m-1][j]=Math.max(dp[m-1][j+1]-dungeon[m-1][j],1);
        }
        for(int i=m-2;i>=0;i--){
            dp[i][n-1]=Math.max(dp[i+1][n-1]-dungeon[i][n-1],1);
        }
        // 循环计算每个格子的初始血量
        for(int i=m-2;i>=0;i--){
            for(int j=n-2;j>=0;j--){
                dp[i][j]=Math.max(Math.min(dp[i][j+1],dp[i+1][j])-dungeon[i][j],1);
            }
        }
        return dp[0][0];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值