leetcode_174_地下城游戏

该题很难,思路很难想到。。。


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

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

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

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

 

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

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

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

说明:

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/dungeon-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路:很明显,这是一个动态规划问题。和315号问题有点类似,但是做到最后发现有很大的不同:这里影响状态转移的因子有两个,而且这两个因子的重要性一样,这也是困扰了自己一上午的麻烦。。。导致无法完全实现。。。

后来看了提示,才知道还可以逆向DP,从右下角终点开始DP,直到左上角起点结束。这样DP的话,只需要考虑是否是最小即可,即只有单因素影响。

策略:

初始化终点:(这个非常重要!!!)

从dungeon[row-1][column-1]开始,如果其值>0,则将其值为0,含义为:在[row-1][column-1]处王子会掉的血量。很显然,如果是正数,则不会掉血。所以将其置为0。

初始化右边界:

如下:

//初始化
        //右边界
        for(int i = row-2;i >= 0;i--){
            int c = dungeon[i][column-1] + dungeon[i+1][column-1];
            dungeon[i][column-1] = c>0? 0 : c;
        }

初始化下边界:

//下边界
        for(int i = column-2;i >= 0;i--){
            int c = dungeon[row-1][i] + dungeon[row-1][i+1];
            dungeon[row-1][i] = c>0? 0 : c;
        }

策略和初始化终点有一点不相同:因为王子可以向右或者向下走,反过来的时候就是可以向左或者向上走。所以:

dungeon[i][j] = Max(dungeon[i+1][j],dungeon[i][j+1])+ dungeon[i][j]。

又因为下边界处i+1是越界的,右边界处j+1是越界的,所以只有其中的一个。。。

至于中间的部分,则是两个部分都有。

遍历:

策略和上面初始化策略一样:

for(int i = row-2;i >= 0;i--){
            for(int j = column-2;j >= 0;j--){
                int c = Math.max(dungeon[i+1][j], dungeon[i][j+1]) + dungeon[i][j];
                dungeon[i][j] = c>0? 0 : c;
            }
        }

实现:

JAVA:

package leetcode;

/*
USER:LQY
DATE:2020/7/12
TIME:9:31
*/
public class leetcode_174 {
    public static void main(String []args){
        int [][]dungeon = {{-2,-3,3},{-5,-10,1},{10,30,-5}};
        System.out.println(new leetcode_174().calculateMinimumHP1(dungeon));
    }
//    逆向DP。AC
    public int calculateMinimumHP1(int [][]dungeon){
        int row = dungeon.length;
        int column = dungeon[0].length;
        if (row == column && row == 1) {
            if (dungeon[0][0] > 0) return 1;
            else return -dungeon[0][0] + 1;
        }

        //初始化
        dungeon[row-1][column-1] = dungeon[row-1][column-1]>0? 0:dungeon[row-1][column-1];
        //右边界
        for(int i = row-2;i >= 0;i--){
            int c = dungeon[i][column-1] + dungeon[i+1][column-1];
            dungeon[i][column-1] = c>0? 0 : c;
        }
        //下边界
        for(int i = column-2;i >= 0;i--){
            int c = dungeon[row-1][i] + dungeon[row-1][i+1];
            dungeon[row-1][i] = c>0? 0 : c;
        }
        for(int i = row-2;i >= 0;i--){
            for(int j = column-2;j >= 0;j--){
                int c = Math.max(dungeon[i+1][j], dungeon[i][j+1]) + dungeon[i][j];
                dungeon[i][j] = c>0? 0 : c;
            }
        }
        return -dungeon[0][0] + 1;
    }
//  正常DP。无法实现。。。
    public int calculateMinimumHP(int[][] dungeon) {
        int row = dungeon.length;
        int column = dungeon[0].length;
        if (row == column && row == 1) {
            if (dungeon[0][0] > 0) return 1;
            else return -dungeon[0][0] + 1;
        }

        int n = Math.max(row, column);
        int [][]ob = new int[n][n];
        int [][]dp = new int[n][n];
        int [][]sum = new int[n][n];
//      初始化ob
        for(int i = 0;i < row;i++){
            for(int j = 0;j < column;j++){
                ob[i][j] = dungeon[i][j];
            }
        }
        dp[0][0] = ob[0][0];
        sum[0][0] = ob[0][0];
        //初始化sum的上边界与左边界
        for(int j = 1;j < column;j++){
            sum[0][j] = sum[0][j-1] + ob[0][j];
            dp[0][j] = Math.min(sum[0][j], dp[0][j-1]);
        }
        for(int i = 1;i < row;i++){
            sum[i][0] = sum[i-1][0] + ob[i][0];
            dp[i][0] = Math.min(dp[i-1][0], sum[i][0]);
        }
        int nrow = 1;
        int ncolumn = 1;
        int [][]dir = {{0,1},{1,0}};
        while(nrow<row && ncolumn<column){
            //先更新对角线处
            int flag;
            //此时不需要判断是否越界
            if(sum[nrow-1][ncolumn] > sum[nrow][ncolumn-1]){
                //从上面来
                flag = -1;
                sum[nrow][ncolumn] = sum[nrow-1][ncolumn] + ob[nrow][ncolumn];

            }else if(sum[nrow-1][ncolumn] < sum[nrow][ncolumn-1]){
                //从左边来
                flag = 1;
                sum[nrow][ncolumn] = sum[nrow][ncolumn-1] + ob[nrow][ncolumn];
            }else{
                //此时需要借助dp
                if(dp[nrow-1][ncolumn] > dp[nrow][ncolumn-1]){
                    //从上面来
                    flag = -1;
                    sum[nrow][ncolumn] = sum[nrow-1][ncolumn] + ob[nrow][ncolumn];
                }else{
                    //从左边来
                    flag = 1;
                    sum[nrow][ncolumn] = sum[nrow][ncolumn-1] + ob[nrow][ncolumn];
                }
            }
            //再更新此处的dp
            if(flag == -1){
                //从上面来
                dp[nrow][ncolumn] = Math.min(dp[nrow-1][ncolumn], sum[nrow][ncolumn]);

            }else if(flag == 1){
//                从左边来

                dp[nrow][ncolumn] = Math.min(dp[nrow][ncolumn-1], sum[nrow][ncolumn]);
            }
            //最后更新当前所在的行和列
            updateRowAndColumn(ob, sum, dp, nrow, ncolumn, row, column);
            nrow++;
            ncolumn++;
        }
        print(sum, row, column);
        print(dp, row, column);

        return dp[row-1][column-1];
    }
    public void updateRowAndColumn(int [][]ob, int [][]sum, int [][]dp, int sr, int sc, int row, int column){

        for(int i = sr+1; i < row;i++){
            //先更新对角线处
            int flag = 0;
            //此时不需要判断是否越界
            if(sum[i-1][sc] > sum[i][sc-1]){
                //从上面来
                flag = -1;
                sum[i][sc] = sum[i-1][sc] + ob[i][sc];
            }else if(sum[i-1][sc] < sum[i][sc-1]){
                //从左边来
                flag = 1;
                sum[i][sc] = sum[i][sc-1] + ob[i][sc];
            }else if(sum[i-1][sc] == sum[i][sc-1]){
                //此时需要借助dp
                if(dp[i-1][sc] > dp[i][sc-1]){
                    //从上面来
                    flag = -1;
                    sum[i][sc] = sum[i-1][sc] + ob[i][sc];
                }else{
                    //从左边来
                    flag = 1;
                    sum[i][sc] = sum[i][sc-1] + ob[i][sc];
                }
            }
            //再更新此处的dp

            if(flag == -1){
                dp[i][sc] = Math.min(dp[i-1][sc], sum[i][sc]);
            }else if(flag == 1){
                dp[i][sc] = Math.min(dp[i][sc-1], sum[i][sc]);
            }

        }
        for(int j = sc+1;j < column;j++){
            //先更新对角线处
            int flag = 0;
            //此时不需要判断是否越界
            if(sum[sr-1][j] > sum[sr][j-1]){
                //从上面来
                flag = -1;
                sum[sr][j] = sum[sr-1][j] + ob[sr][j];
            }else if(sum[sr-1][j] < sum[sr][j-1]){
                //从左边来
                flag = 1;
                sum[sr][j] = sum[sr][j-1] + ob[sr][j];
            }else if(sum[sr-1][j] == sum[sr][j-1]){
                //此时需要借助dp
                if(dp[sr-1][j] > dp[sr][j-1]){
                    //从上面来
                    flag = -1;
                    sum[sr][j] = sum[sr-1][j] + ob[sr][j];
                }else{
                    //从左边来
                    flag = 1;
                    sum[sr][j] = sum[sr][j-1] + ob[sr][j];
                }
            }
            //再更新此处的dp

            if(flag == -1){
                dp[sr][j] = Math.min(dp[sr-1][j], sum[sr][j]);
            }else if(flag == 1){
                dp[sr][j] = Math.min(dp[sr][j-1], sum[sr][j]);
            }
        }

    }

    public void print(int [][]nums, int row, int column){
        for(int i = 0;i < row;i++){
            for(int j = 0;j < column;j++){
                System.out.print(nums[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println();
    }

}

python:

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        row = len(dungeon)
        column = len(dungeon[0])
        if(row==column and row==1):
            if(dungeon[0][0] > 0):
                return 1
            else:
                return -dungeon[0][0] + 1

        if(dungeon[row-1][column-1] > 0):
            dungeon[row - 1][column - 1] = 0

        for i in range(row-2, -1, -1):
            c = dungeon[i][column-1] + dungeon[i+1][column-1]
            if(c > 0):
                dungeon[i][column-1] = 0
            else:
                dungeon[i][column-1] = c
        for i in range(column - 2, -1, -1):
            c = dungeon[row-1][i] + dungeon[row-1][i+1]
            if (c > 0):
                dungeon[row-1][i] = 0
            else:
                dungeon[row-1][i] = c

        for i in range(row-2, -1, -1):
            for j in range(column-2, -1, -1):
                c = max(dungeon[i+1][j], dungeon[i][j+1]) + dungeon[i][j]
                if(c > 0):
                    dungeon[i][j] = 0
                else:
                    dungeon[i][j] = c
        return -dungeon[0][0] + 1

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值