力扣174

174. 地下城游戏

力扣题解添加链接描述
难度困难555

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快到达公主,骑士决定每次只向右或向下移动一步。

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7

-2 (K)-33
-5-101
1030-5 §

说明:

  • 骑士的健康点数没有上限。
  • 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
解析:
  1. 合理设计dp数组/函数的定义。类比前文 最小路径和问题dp函数签名肯定长这样:
int dp(int[][] grid, int i, int j);
  1. dp函数的定义应该是:

    grid[i][j]到达终点(右下角)所需的最少生命值是dp(grid, i, j)

    按照最小路径和dp数组的定义,只知道「能够从左上角到达B的最小生命值」,但并不知道「到达B时的生命值」。

    但是「到达B时的生命值」是进行状态转移的必要参考,我给你举个例子你就明白了,假设下图这种情况:

    图片

    你说这种情况下,骑士救公主的最优路线是什么?

    显然是按照图中蓝色的线走到B,最后走到A对吧,这样初始血量只需要 1 就可以;如果走黄色箭头这条路,先走到C然后走到A,初始血量至少需要 6。

    为什么会这样呢?骑士走到BC的最少初始血量都是 1,为什么最后是从B走到A,而不是从C走到A呢?

    因为骑士走到B的时候生命值为 11,而走到C的时候生命值依然是 1。

    如果骑士执意要通过C走到A,那么初始血量必须加到 6 点才行;而如果通过B走到A,初始血量为 1 就够了,因为路上吃到血瓶了,生命值足够抗A上面怪物的伤害。

    这下应该说的很清楚了,再回顾我们对dp函数的定义,上图的情况,算法只知道dp(1, 2) = dp(2, 1) = 1,都是一样的,怎么做出正确的决策,计算出dp(2, 2)呢?,所以要修改dp的定义

  2. base case 就是i=m - 1, j= n - 1的时候,保证骑士不能死需要的生命值。

     if (i == m - 1 && j == n - 1) {
            return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1;
        }
    
  3. 状态转移:根据新的dp函数定义和 base case,我们想求dp(0, 0),那就应该试图通过dp(i, j+1)dp(i+1, j)推导出dp(i, j),这样才能不断逼近 base case,正确进行状态转移。

    「从A到达右下角的最少生命值」应该由「从B到达右下角的最少生命值」和「从C到达右下角的最少生命值」推导出来:

    图片

    能不能推导出来呢?这次是可以的,假设dp(0, 1) = 5, dp(1, 0) = 4,那么可以肯定要从A走向C,因为 4 小于 5 嘛。

    那么怎么推出dp(0, 0)是多少呢?

    假设A的值为 1,既然知道下一步要往C走,且dp(1, 0) = 4意味着走到grid[1][0]的时候至少要有 4 点生命值,那么就可以确定骑士出现在A点时需要 4 - 1 = 3 点初始生命值,对吧。

    那如果A的值为 10,落地就能捡到一个大血瓶,超出了后续需求,4 - 10 = -6 意味着骑士的初始生命值为负数,这显然不可以,骑士的生命值小于 1 就挂了,所以这种情况下骑士的初始生命值应该是 1。

    综上,状态转移方程已经推出来了:

    int res = min(
        dp(i + 1, j),
        dp(i, j + 1)
    ) - grid[i][j];
    
    dp(i, j) = res <= 0 ? 1 : res;
    
    1. 备忘录消除重叠子问题
    代码:
class Solution {
    int[][] memo;
    public int calculateMinimumHP(int[][] dungeon) {
        int m=dungeon.length;
        int n=dungeon[0].length;
        memo=new int[m+1][n+1];
        for(int[] row: memo){
            Arrays.fill(row,-1);
        }
        return dp(dungeon,0,0);//从左上角到右下角需要的最少的生命值
    }
    public int dp(int[][] dungeon,int i,int j){
        int m=dungeon.length;
        int n=dungeon[0].length;
        //basecase
        if(i==m-1 && j== n-1) return dungeon[i][j]>=0? 1:-dungeon[i][j]+1;
        if(i==m || j==n) return Integer.MAX_VALUE;
        if(memo[i][j]!=-1) return memo[i][j];
        int res=Math.min(dp(dungeon,i+1,j),dp(dungeon,i,j+1))-dungeon[i][j];
        memo[i][j]=res<=0? 1:res;
        return memo[i][j];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值