leetcode 174. 地下城游戏

问题描述

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球
(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

-2 (K)	-3	3
-5	-10	1
10	30	-5 (P)
 

说明:

骑士的健康点数没有上限。

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

问题分析

建立一个二维数组 dp,其中 dp[i][j] 用来表示当前位置 (i, j) 出发的起始血量,最先处理的是公主所在的房间的起始生命值,然后慢慢向第一个房间扩散,不断的得到各个位置的最优的生命值,注意要从右下角开始扫描

代码

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(),i,j,n = dungeon[0].size();
        //dp[i][j]为(i,j)点到右下角需要的最低血量
        vector<vector<int>> dp(m,vector<int>(n,0));
        //如果右下角的数大于0则骑士需要血量为1就好了,否则需要补血-dungeon[m-1][n-1]+1,+1是补血后至少还有一格血
        dp[m-1][n-1] = dungeon[m-1][n-1] > 0?1:-dungeon[m-1][n-1]+1;
        //对于最后一列除了右下角的部分(i,n-1),如果数字大于到达下一个个格(i+1,n-1)的初始血量,则骑士需要血量为1就好了,否则需要补血dp[i+1][n-1]-dungeon[i][n-1]
        for(i = m-2; i >= 0; --i)
            dp[i][n-1] = dungeon[i][n-1]>=dp[i+1][n-1]?1:dp[i+1][n-1]-dungeon[i][n-1];
        //对于最后一行除了右下角的部分(m-1,j),如果数字大于到达下一个格(m-1,j+1)的初始血量,则骑士需要血量为1就好了,否则需要补血dp[m-1][j+1]-dungeon[m-1][j]
        for(j = n-2; j >= 0; --j)
            dp[m-1][j] = dungeon[m-1][j]>=dp[m-1][j+1]?1:dp[m-1][j+1]-dungeon[m-1][j];
        for(i = m-2; i >= 0; --i)
        {
            for(j = n-2; j >= 0; --j)
            {
                //先找到从当前格的下一个格的最小初始血量
                int minval = min(dp[i][j+1],dp[i+1][j]);
                //如果当前格的数字大于那个最小初始血量,则骑士需要血量为1就好了,否则需要补血
                dp[i][j] = dungeon[i][j] >= minval?1:minval - dungeon[i][j];
            }
        }
        return dp[0][0];
    }
    
};

结果

执行用时:12 ms, 在所有 C++ 提交中击败了80.82%的用户
内存消耗:8.9 MB, 在所有 C++ 提交中击败了100.00%的用户

代码2

进行空间优化:

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(),i,j,n = dungeon[0].size();
        //很巧妙多存了一个数字,实际上认为规模是m\times n+1(原先规模为m\times n)
        vector<int> dp(n+1,INT_MAX);     
        //因为对于虚拟的(m,n),dp[n]=INT_MAX,这时进入(m,n-1)血量最低就可以了
        dp[n-1] = 1;
        for(i = m-1; i >= 0; --i)
        {
            for(j = n-1; j >= 0; --j)
            {
            
                int minval = min(dp[j],dp[j+1]);
                dp[j] = dungeon[i][j] >= minval?1:minval - dungeon[i][j];
            }
        }
        return dp[0];
    }
    
};

结果

执行用时:12 ms, 在所有 C++ 提交中击败了80.82%的用户
内存消耗:8.4 MB, 在所有 C++ 提交中击败了100.00%的用户

代码3(递归)

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(),i,j,n = dungeon[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));
        return findpath(dungeon,dp,0,0);
    }
    int findpath(vector<vector<int>>& dungeon,vector<vector<int>>& dp,int x,int y)
    {
        int m = dungeon.size(),n = dungeon[0].size(),ans = 0;
        //来到右下角保证至少有一滴血
        if(x == m-1 && y == n-1)
            //return max(1 - dungeon[x][y], 1);
            return dungeon[m-1][n-1] > 0?1:-dungeon[m-1][n-1]+1;      
        if(dp[x][y])return dp[x][y];
        if(x == m-1)
        {
            int minval = findpath(dungeon,dp,x,y+1);
            ans = dungeon[x][y] >= minval?1:minval-dungeon[x][y];
        }
         //在最下面走只能向右走,这里的返回值相当于动态规划里的dp,为该点所需的最低健康值
         //注意等号
        else if(y == n-1){
            int minval = findpath(dungeon,dp,x+1,y);
            ans =  dungeon[x][y] >= minval?1:minval-dungeon[x][y];
        }
        else{
            int minval = min(findpath(dungeon,dp,x+1,y),findpath(dungeon,dp,x,y+1));
            ans =  (dungeon[x][y] >= minval?1:minval-dungeon[x][y]);
        }
        return dp[x][y] = ans;
    }
    
};

结果

执行用时:12 ms, 在所有 C++ 提交中击败了80.91%的用户
内存消耗:8.7 MB, 在所有 C++ 提交中击败了100.00%的用户
posted @ 2020-07-13 12:19  曲径通霄  阅读(111)  评论(0编辑  收藏  举报