文章目录

  • 碎碎念
  • 题目描述
  • 逆推
  • 滚动数组优化空间
  • 思路
    • 定义状态
    • 状态转移方程
碎碎念

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

前几天为了数模睡得太少了(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;}
};

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

定义状态

dp(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)格子出发,到达(n−1,m−1)所需要的最小血量;该血量并没有受到第(i,j)格子上的事件的影响

也就是说

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

状态转移方程

v=dungeon(i,j),当前地牢的值v = dungeon(i,j), 当前地牢的值 v=dungeon(i,j),当前地牢的值

dp(i,j)=min(dp(i,j+1),dp(i+1,j))−v,ifv<0dp(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
如果当前地牢的是怪物,值为负数,说明到达这个地牢之前的最小血量为 当前地牢可以到达的向下,向右地牢 的最小血量再加上扣掉的这个血量。
dp(i,j)=min(dp(i,j+1),dp(i+1,j))−v,ifv>0,v<min(dp(i,j+1),dp(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))
如果当前地牢可以回血,但是回的血量还不足与支撑走到结尾,那么最小的血量应该是 右边,下边的可以到达结尾的最小血量 减去回复的血量。
dp(i,j)=1,ifv>=min(dp(i,j+1),dp(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

公主救骑士---地下城游戏_leetcode相关推荐

  1. 力扣174. 地下城游戏

    力扣174. 地下城游戏 文章目录 力扣174. 地下城游戏 一.题目描述 二.分析 三.完整代码 一.题目描述 二.分析 这个题一看就可以用动态规划,就需要确定动态规划的状态和选择以及状态转移方程 ...

  2. leetcode题库174 地下城游戏

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

  3. leetcode 思路——64. 最小路径和——174. 地下城游戏

    leetcode 思路--64. 最小路径和--174. 地下城游戏 64. 最小路径和 174. 地下城游戏 64. 最小路径和 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角 ...

  4. [算法]LeetCode每日一题--174. 地下城游戏(Java)

    DailyChallenge 174. 地下城游戏 Hard20200712 Description 一些恶魔抓住了公主(P)并将她关在了地下城的右下角.地下城是由 M x N 个房间组成的二维网格. ...

  5. ​LeetCode刷题实战174:地下城游戏

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  6. Java实现 LeetCode 174 地下城游戏

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

  7. 力扣——174.地下城游戏(困难难度)——万能的递归与动态分析

    力扣--174. 地下城游戏 一.算法目录合集 1.地址 2.说明 二.题目说明 1.题干 2.原地址 三.实现步骤 1.思路分析 1.1.分析问题 1.2.具体步骤 ① 特殊情况分析 ② 常规分析 ...

  8. 【中等】龙与地下城游戏问题-Java:经典动态规划解法

    分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击人工智能教程 package live.every.day.ProgrammingDesign.Codi ...

  9. leetcode174. 地下城游戏(java)

    地下城游戏 leetcode174. 地下城游戏 题目描述 动态规划 解题思路 代码 动态规划专题 leetcode174. 地下城游戏 来源:力扣(LeetCode) 链接:https://leet ...

  10. 动态规划——地下城游戏

    题目链接 leetcode在线oj题--地下城游戏 题目描述 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 .地下城是由 m x n 个房间组成的二维网格.我们英勇的骑士最初被安置在 ...

最新文章

  1. mysql约束日期_MySQL的约束、事务、字符串、日期、数学相关及其他补充
  2. .Net中的设计模式——Iterator模式
  3. 002-docker17.06安装
  4. 数组去重是面试中经常问到的问题
  5. 为什么 在内存中为什么 0xffff 是 -1
  6. Linux之交互式scripts
  7. MySQL分组函数的使用特点
  8. 事件处理之一:两种方式:监听器与回调
  9. 计算机网络应用基础论文,计算机网络应用基础概述论文
  10. 计算机excel表格相关考试视频,1189.5天通过职称计算机考试:Excel 2003中文电子表格(考点视频串讲+全真模拟).pdf...
  11. 龙武2服务器在维护,龙武5.25更新维护时间_龙武5.5更新维护详情_牛游戏网
  12. 2016年12月20日感想
  13. java mail 554_554邮件被拒绝:电子邮件地址未经验证[重复]
  14. 为什么用dict.get(key)而不是dict [key]?
  15. Form的显式方式。
  16. 什么是java OOM Out Of Memory 内存溢出?如何分析及解决oom问题?
  17. javascript滚动条响应鼠标滑轮的实现上下滚动事件
  18. 最新的SAS SID 2023可用至2023年1月SAS 9.4 SID续订更新sas sid 2022服务器通用版server
  19. 为了IT,加强身体锻炼之【双盘腿打坐的好处】
  20. 微信打开跳转浏览器php代码,简单通用QQ/微信跳转浏览器打开代码

热门文章

  1. 让Vim打造成强大的IDE,附_vimrc的配置和使用
  2. 人行征信中心提醒:不要随意授权征信查询!
  3. 留在一线城市还是回老家?一个8年北漂的4点思考
  4. android h5 qq登录,Android webview一键登录手机QQ(2018.11)
  5. Navicat导入连接
  6. mshtml组件引用的问题 (转)
  7. 华科计算机硕士毕业论文,华中科技大学硕士毕业论文要求_华中科技大学2020年硕士招生简章_华中科技大学研究生院...
  8. 微型计算机硬件系统基本组成是什么,计算机硬件系统基本组成有什么?
  9. c++:过滤多余的空格
  10. 专业技能与职业素养报告计算机,职业技能与职业素养的心得体会