公主救骑士---地下城游戏_leetcode

碎碎念

昨天写了半天正推之后,突然意识到这题其实是逆推的题。但是当时和现在一样,困到不想写代码了。于是就去睡觉了。

前几天为了数模睡得太少了(4天没我正常睡2天多)但是当时确实还不困,坚持完了星期一的满课。现在亢奋期过去了,天天睡9-10小时了。晚上10点就想睡觉了。硬撑着写完了逆推和滚动数组的代码。赶紧写完睡觉。

题目描述

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

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

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

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

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
链接:https://leetcode.cn/problems/dungeon-game

输入:
[	
	[-2,-3,3],
	[-5,-10,1],
	[10,30,-5]
]
输出:
7
逆推
//自己写的复杂版本 可以跳过 牛客代码
#include <stdio.h>

int map[1001][1001];
int dp[1001][1001];
int main() {
    int n, m;
    while (scanf("%d %d", &n, &m) != EOF) { // 注意 while 处理多个 case
        // 64 位输出请用 printf("%lld")
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                scanf("%d", &map[i][j]);
            }
        }
        int v, min;
        dp[n - 1][m - 1] = map[n - 1][m - 1] >= 0 ? 1 : 1 - map[n - 1][m - 1];
        for (int j = m - 2; j >= 0; --j) {
            v = map[n - 1][j];
            if (v < 0) {
                dp[n - 1][j] = dp[n - 1][j + 1] - v;
            } else {
                dp[n - 1][j] = v >= dp[n - 1][j + 1] ? 1 : dp[n - 1][j + 1] - v;
            }
        }
        for (int i = n - 2; i >= 0; --i) {
            for (int j = m - 1; j >= 0; --j) {
                v = map[i][j];
                if (j == m - 1) {
                    if (v < 0) {
                        dp[i][j] = dp[i + 1][j] - v;
                    } else {
                        dp[i][j] = v >= dp[i+1][j] ? 1 : dp[i+1][j] - v;
                    }
                } else {
                    min = dp[i][j+1] > dp[i+1][j] ? dp[i+1][j] : dp[i][j+1];
                    if(v < 0) {
                        dp[i][j] = min - v;
                    } else {
                        dp[i][j] = v >= min ? 1 : min - v;
                    }
                }
            }
        }

        printf("%d", dp[0][0]);
        //输出查看结果
        for (int i = 0; i < n; ++i) {
            printf("\n");
            for (int j = 0; j < m; ++j) {
                printf("%d ", dp[i][j]);
            }
        }

        
    }
    return 0;
}
//leetcode 代码
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int n = dungeon.size(), m = dungeon[0].size();
        vector<vector<int> > dp(n, vector<int>(m));

        int v, minN;
        dp[n-1][m-1] = dungeon[n-1][m-1] >= 0 ? 1 : 1 - dungeon[n-1][m-1];
        for(int j = m-2; j >= 0; --j) {
            v = dungeon[n-1][j];
            minN = dp[n-1][j+1];
            if(v < 0) {
                dp[n-1][j] = minN - v;
            } else {
                dp[n-1][j] = v >= minN ? 1 : minN - v; 
            }
        }

        for(int i = n-2; i >= 0; --i) {
            for(int j = m-1; j >= 0; --j) {
                v = dungeon[i][j];
                if(j == m-1) {
                    minN = dp[i+1][j];
                } else {
                    minN = min(dp[i+1][j], dp[i][j+1]);
                }

                if(v < 0) {
                    dp[i][j] = minN - v;
                } else {
                    dp[i][j] = v >= minN ? 1 : minN - v;
                }
            }
        }

        return dp[0][0];
    }
};
// 官方的简单解答 
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int n = dungeon.size(), m = dungeon[0].size();
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, INT_MAX));
        dp[n][m - 1] = dp[n - 1][m] = 1;
        for (int i = n - 1; i >= 0; --i) {
            for (int j = m - 1; j >= 0; --j) {
                int minn = min(dp[i + 1][j], dp[i][j + 1]);
                dp[i][j] = max(minn - dungeon[i][j], 1);
            }
        }
        return dp[0][0];
    }
};
滚动数组优化空间
//自己写的复杂代码 可以跳过
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int n = dungeon.size(), m = dungeon[0].size();
        vector<int> dp(m);

        int v, minN;
        dp[m-1] = dungeon[n-1][m-1] >= 0 ? 1 : 1 - dungeon[n-1][m-1];
        for(int j = m-2; j >= 0; --j) {
            v = dungeon[n-1][j];
            minN = dp[j+1];
            if(v < 0) {
                dp[j] = minN - v;
            } else {
                dp[j] = v >= minN ? 1 : minN - v; 
            }
        }

        for(int i = n-2; i >= 0; --i) {
            for(int j = m-1; j >= 0; --j) {
                v = dungeon[i][j];
                if(j == m-1) {
                    minN = dp[j];
                } else {
                    minN = min(dp[j], dp[j+1]);
                }

                if(v < 0) {
                    dp[j] = minN - v;
                } else {
                    dp[j] = v >= minN ? 1 : minN - v;
                }
            }
        }

        return dp[0];
    }
};
//基于官解的 滚动数组优化 
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int n = dungeon.size(), m = dungeon[0].size();
        vector<int> dp(m + 1, INT_MAX);
        dp[m] = 1;
        for (int i = n - 1; i >= 0; --i) {
            for (int j = m - 1; j >= 0; --j) {
                int minn = min(dp[j], dp[j + 1]);
                dp[j] = max(minn - dungeon[i][j], 1);
            }
            dp[m] = INT_MAX;//注意此处 因为只有最后一排才是 出发点 而其他地方都是不能到达的
        }
        return dp[0];
    }
};

思路

这题正推其实也能推,但是过于麻烦了。正推遇到的最大问题就是存在一个回血该怎么和最小血量统一。这个时候其实记录下两个数值就行了。但是嫌这玩意写的蛮麻烦。

看到别人写的代码如下。

#define pii pair<int, int>
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m=dungeon.size(),n=dungeon[0].size();
        vector<vector<vector<pii>>> dp(m,vector<vector<pii>> (n));
        dp[0][0].push_back({dungeon[0][0],dungeon[0][0]});
        for(int i=1;i<m;i++){
            int cur=dp[i-1][0][0].second+dungeon[i][0];
            dp[i][0].push_back({min(dp[i-1][0][0].first,cur),cur});
        }
        for(int i=1;i<n;i++){
            int cur=dp[0][i-1][0].second+dungeon[0][i];
            dp[0][i].push_back({min(dp[0][i-1][0].first,cur),cur});
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                vector<pii> tmp;
                for(auto x:dp[i-1][j]){
                    tmp.push_back({min(x.second+dungeon[i][j],x.first),x.second+dungeon[i][j]});
                }
                for(auto x:dp[i][j-1]){
                    tmp.push_back({min(x.second+dungeon[i][j],x.first),x.second+dungeon[i][j]});
                }
                sort(tmp.begin(),tmp.end());
                dp[i][j].push_back(tmp.back());
                tmp.pop_back();
                int th=dp[i][j][0].second;
                while(!tmp.empty()){
                    if(tmp.back().second>th){
                        th=tmp.back().second;
                        dp[i][j].push_back(tmp.back());
                    }
                    tmp.pop_back();
                }
            }
        }
        return max(-dp[m-1][n-1][0].first+1,1);
    }
};

这题也能二分,因为如果当前血量可以通过,那么大于这个血量的所有血量都能过;如果当前血量不能过,那么小于当前血量的肯定也不能过。这是单调的,可以二分。然后每次判断一下血量能不能过,逐渐缩小范围,就行了。不过复杂度比用dp高。

看到别人的代码如下

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size(), n = dungeon[0].size();

        int L = 1, R = 1e9;
        while(L < R) {
            int M = (L + R) / 2;
            vector<vector<int>> dp(m, vector<int>(n));
            dp[0][0] = M + dungeon[0][0];
            for(int i = 0; i < m; i += 1) {
                for(int j = 0; j < n; j += 1) {
                    if(i && dp[i - 1][j] > 0)
                        dp[i][j] = max(dp[i][j], dp[i - 1][j] + dungeon[i][j]);
                    if(j && dp[i][j - 1] > 0)
                        dp[i][j] = max(dp[i][j], dp[i][j - 1] + dungeon[i][j]);
                }
            }
            if(dp[m - 1][n - 1] > 0) R = M;
            else L = M + 1;
        }
        return L;
    }
};

回到当前的思路。为什么是逆推?因为逆推不用再管正数了。逆推我已经假设当前血量就能到达结尾了。遇到正数,就看看它是不是比我当前的血量大,大的话,说明回复的血量就已经够我再继续去救公主了,小的话,说明还需要一定的血量去支撑。而不是像正推一样,不知道该不该加上它。

定义状态

d p ( i , j ) : 从第 ( i , j ) 格子出发,到达 ( n − 1 , m − 1 ) 所需要的最小血量 ; 该血量并没有受到第 ( i , j ) 格子上的事件的影响 dp(i,j) : 从第(i,j)格子出发,到达(n-1,m-1)所需要的最小血量; 该血量并没有受到第(i,j)格子上的事件的影响 dp(i,j):从第(i,j)格子出发,到达(n1,m1)所需要的最小血量;该血量并没有受到第(i,j)格子上的事件的影响

也就是说

假如地牢如下 【-1】, 那么最小血量不是 1,而是2. dp(0,0) = 2;

状态转移方程

v = d u n g e o n ( i , j ) ,当前地牢的值 v = dungeon(i,j), 当前地牢的值 v=dungeon(i,j),当前地牢的值

d p ( i , j ) = m i n ( d p ( i , j + 1 ) , d p ( i + 1 , j ) ) − v , i f   v < 0 dp(i,j) = min(dp(i,j+1),dp(i+1,j)) - v, if \space v < 0 dp(i,j)=min(dp(i,j+1),dp(i+1,j))v,if v<0
如果当前地牢的是怪物,值为负数,说明到达这个地牢之前的最小血量为 当前地牢可以到达的向下,向右地牢 的最小血量再加上扣掉的这个血量。
d p ( i , j ) = m i n ( d p ( i , j + 1 ) , d p ( i + 1 , j ) ) − v , i f   v > 0 ,   v < m i n ( d p ( i , j + 1 ) , d p ( i + 1 , j ) ) dp(i,j) = min(dp(i,j+1),dp(i+1,j)) - v, if \space v > 0,\space v < min(dp(i,j+1),dp(i+1,j)) dp(i,j)=min(dp(i,j+1),dp(i+1,j))v,if v>0, v<min(dp(i,j+1),dp(i+1,j))
如果当前地牢可以回血,但是回的血量还不足与支撑走到结尾,那么最小的血量应该是 右边,下边的可以到达结尾的最小血量 减去回复的血量。
d p ( i , j ) = 1 ,   i f   v > = m i n ( d p ( i , j + 1 ) , d p ( i + 1 , j ) ) dp(i,j) = 1, \space if \space v >= min(dp(i,j+1),dp(i+1,j)) dp(i,j)=1, if v>=min(dp(i,j+1),dp(i+1,j))
如果回复的血量都已经够你走到结尾了,那么你达到这个地牢之前的血量只要不是0就行了,于是就是1.

稍微统一一下上面三种情况就可以得到。

如果 v < min(dp(i+1,j), dp(i,j+1)) 那么 最小血量为 min(dp(i+1,j), dp(i,j+1)) - v

否则 为 1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值