动态规划-地下城游戏

在上一篇博客中,我们了解两个关于路径的问题,在这一篇中,我们将进一步了解一道更加困难的问题

题目描述

恶魔们抓住了公主并将她关在了地下城的右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。 为了尽快解救公主,骑士决定每次只 向右向下 移动一步。 返回确保骑士能够拯救到公主所需的最低初始健康点数。

注意:任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间

174. 地下城游戏 - 力扣(LeetCode)

解题思路

在这个问题中,我们定义dp[i][j]为骑士从网格中的(i, j)位置(即第i行第j列的房间)出发,到达终点(右下角)所需的最低初始健康点数。

初始化

  • 首先,我们需要考虑边界情况。由于骑士必须从左上角出发,因此dp[0][0]应该根据dungeon[0][0]的值进行调整。如果dungeon[0][0]是负的,那么骑士至少需要-dungeon[0][0] + 1的健康点数来确保从起点出发时不会立即死亡。如果dungeon[0][0]是非负的,那么骑士的初始健康点数可以设为1(因为已经足够应对该房间的情况)。

  • 对于其他边界位置(第一行和第一列),我们需要确保骑士在到达这些位置时不会死亡,并计算到达这些位置所需的最低健康点数。这通常涉及向前看(向右或向下),以确定从当前位置到边界的下一个安全点所需的健康点数。

状态转移方程

对于网格中的非边界位置(i, j),我们可以根据以下规则计算dp[i][j]

  • 如果骑士从(i, j)向右移动到(i, j+1)是安全的(即dp[i][j+1]存在且不会导致死亡),则dp[i][j]可以是min(1, dp[i][j+1] - dungeon[i][j])。这里的1表示骑士至少需要1点健康点数才能存活,而dp[i][j+1] - dungeon[i][j]表示从(i, j)向右移动后到达(i, j+1)时所需的健康点数。

  • 如果骑士从(i, j)向下移动到(i+1, j)是安全的,则类似地,dp[i][j]可以是min(1, dp[i+1][j] - dungeon[i][j])

  • 但是,骑士不能同时向右和向下移动,因此我们需要取上述两种情况中的较大值,以确保无论选择哪个方向,骑士都有足够的健康点数。即:

    dp[i][j]=max(1,min(dp[i][j+1]−dungeon[i][j],dp[i+1][j]−dungeon[i][j]))

    注意,如果dungeon[i][j]是一个很大的正数,那么骑士在到达(i, j)后可能会拥有超过dp[i][j+1]dp[i+1][j]的健康点数。然而,我们关心的是确保骑士在到达(i, j)时至少拥有1点健康点数,并且有足够的健康点数来应对接下来的移动。

特殊情况处理

  • 当骑士接近右下角时,他/她需要确保有足够的健康点数来应对最后一个房间(即公主所在的房间)。因此,在计算dp[m-1][n-1]时,我们需要确保dp[m-1][n-1] - dungeon[m-1][n-1] >= 1,即使这意味着dp[m-1][n-1]需要被设置为一个相对较大的值。

通过这种方法,我们可以从右下角开始,逆向填充dp数组,直到左上角。最终,dp[0][0]将包含骑士从左上角出发,确保能够拯救公主所需的最低初始健康点数。

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size();
        int n = dungeon[0].size();
        // 创建一个DP数组,初始化为0
        vector<vector<int>> dp(m, vector<int>(n, 0));

        dp[m - 1][n - 1] = max(1, 1 - dungeon[m - 1][n - 1]); // 右下角特殊处理
        // 初始化最后一列
        for (int i = m - 2; i >= 0; i--) {
            dp[i][n - 1] = max(1, dp[i + 1][n - 1] - dungeon[i][n - 1]);
        }
        // 初始化最后一行
        for (int j = n - 2; j >= 0; j--) {
            dp[m - 1][j] = max(1, dp[m - 1][j + 1] - dungeon[m - 1][j]);
        }
        // 填充DP数组的其他部分
        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                dp[i][j] =
                    max(1, min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j]);
            }
        }
        return dp[0][0];
    }
};

代码解释

  1. 我们首先定义了一个dungeon的二维向量来表示地下城,并初始化了DP数组dp

  2. 我们从右下角开始逆向计算DP数组。因为右下角是终点,所以我们可以直接根据dungeon[m-1][n-1]的值来设置dp[m-1][n-1]

  3. 然后,我们分别初始化DP数组的最后一列和最后一行。这是因为当我们处于这些边界上时,我们只能沿着一个方向移动(向右或向下),所以计算相对简单。

  4. 接下来,我们填充DP数组的其他部分。对于每个位置(i, j),我们查看其右侧和下方的位置,并选择其中所需健康点数较小的那个。但是,我们还需要确保这个值至少为1,因为骑士至少需要1点健康点数才能存活。

  5. 最后,我们返回dp[0][0],即从左上角出发所需的最低初始健康点数。

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值