每日一题:地下城游戏

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

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

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

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

返回确保骑士能够拯救到公主所需的最低初始健康点数。

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

示例 1:

输入:dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
输出:7
解释:如果骑士遵循最佳路径:右 -> 右 -> 下 -> 下 ,则骑士的初始健康点数至少为 7 。

示例 2:

输入:dungeon = [[0]]
输出:1

提示:

  • m == dungeon.length
  • n == dungeon[i].length
  • 1 <= m, n <= 200
  • -1000 <= dungeon[i][j] <= 1000

整体移动方向是从左上到右下,骑士走到第[i ,j]个格子需要的健康点数(血量)和第[i+1 ,j](下一步向右走)/ 第[i,j+1](下一步向下走)相关。因此很容易想到采取动态规划的方式。

正向dp

正向dp,即从王子(左上)向公主(右下)进行规划。先考虑如下这种简单情况:每个格子只扣除血量。

 计算dp[i,j]只需将dp[i+1,j]和dp[i,j+1]中更大的值加上当前[i,j]的值即可。

(注:上面描述的方式中dp中的值是负的,需要的血量取绝对值+1即可)

第一次dp:

第二次dp:

取得到了最终的需要血量 1 - 公主格子中的值 = 16点初始健康点。

 而题目中有的格子是可以回血(正)的。

在这种情况下我们继续考虑正向dp:

如上图所示,如果按照之前的思路,在 -3 和 -8 中我们将选择-3,但是在未来的格子中,存在-20这样一个炸弹值,因此实际上在 [0,0]处选择 -8 是全局最有解。也就是说:

即使知道了 dp[i+1,j]和 dp[i,j+1],也不能根据哪个大就挑哪个(上图第一步选择 -3 还是-8 )。因为有可能 dp[i+1,j] 处的耗血量大(-8),但后续全为加血。而 dp[i,j+1]甚至更深处 dp[i+1][j+1] 是更多的减血,选dp[i+1,j]才是全局最优

以上是比较感性的认知。理性的描述如下:

首先要理解后效性的概念。后效性是动态规划中的一个关键概念,它指一个问题的最优解可以通过其子问题的最优解来构造。换句话说,一个子问题的最优解不会影响其他子问题的最优解。

后效性提供了这点好处:简化了问题的求解,因为我们可以专注于子问题的最优解,而不用考虑它们之间的相互作用。

由于正值的存在,若从正向dp,后面的正值会影响到前面的判断,恰恰破坏了后效性。

考虑刚刚的正向dp,在这个过程其实需要维护两个值:当前所需最小初始生命值和到当前位置的路径和。我们希望需要的最初生命值尽量小,而到当前的路径和尽可能大,产生了两个重要程度相同的参数,正是这一点,破坏了后效性。

(也看到有的资料中描述动态规划是需要无后效性的,这里应该是描述差异,重点在于子问题间不要产生相互作用,也不要产生后向依赖,至于定义出的名词,没什么关系)

注意,也不是说这类问题正向dp完全不能做,可以通过记忆化搜索+动态规划+逻辑完善是可以解决的,但代价实在太大,并且不是最优解。

因而采取反向dp的方式。

继续以上面的例子分析为什么反向dp就不会产生后向依赖的问题?

按照反向dp,第一步结果如上图所示,注意,增加血量不能为之前的损失提供帮助,只会对后续有帮助。因此从在反向dp的过程中如果遇到了计算值 > 0,例如-5 + 30时,需要设置为0,因为你要想能到这个格子,前面扣除的血量是需要的,而不能用后面的加血预先抵扣前面的扣血。

以上只是分析过程,重复这个过程到左上角就不在这里赘述了。

接下来看dp数组的构造以及状态转移方程。

首先这里区分dp数组和原数组dungeon[i][j]的概念。

这是两个数组,虽然dp是依靠原数组计算出的,但分成两个数组看起来更清晰。

考虑问题输出结果:骑士需要的最小血量,注意骑士血量是不能为0的,0就死了,所以最小是1

那么走到dp[i,j]的血量和dp[i+1,j]还有dp[i,j+1]是什么关系?

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

注意这里就是区分原数组和dp数组的好处了,dp数组描述的是:从(i,j)出发,到达终点需要最少的血量。因此要取dp[i+1,j]和dp[i,j+1]中的较小值。而反映在dungeon数组中可能取的就是较大值。

接下来边界条件如何确定?因为起点是右下角,而到这里之后,骑士最低血是1,那么就外扩一行,并将右下格相邻的两个设为1。

而对于其他最外层格子,我们不需要使用到他们,因为dp取的是较小值,所以把他们都设为最大数就不会用到了。

 按上述公式填入对应值:

注意这里为什么是1,也就是公式外层为什么套一层max函数?

因为上文有提到:

按照反向dp,第一步结果如上图所示,注意,增加血量不能为之前的损失提供帮助,只会对后续有帮助。因此从在反向dp的过程中如果遇到了计算值 > 0,例如-5 + 30时,需要设置为0,因为你要想能到这个格子,前面扣除的血量是需要的,而不能用后面的加血预先抵扣前面的扣血。

当时所说设置为0只是在分析,而基于题目要求,骑士最小生命值为1。

进而完善整个dp数组,得到左上角骑士值7:

 代码:

class Solution {
public:int calculateMinimumHP(vector<vector<int>>& dungeon) {int n = dungeon.size(); int 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) {dp[i][j] = max(min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j], 1);}}return dp[0][0];}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/636724.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch实现线性回归模型

在机器学习和深度学习的世界中&#xff0c;线性回归模型是一种基础且广泛使用的算法&#xff0c;简单易于理解&#xff0c;但功能强大&#xff0c;可以作为更复杂模型的基础。使用PyTorch实现线性回归模型不仅可以帮助初学者理解模型的基本概念&#xff0c;还可以为进一步探索更…

【归并】Leetcode 排序数组

题目讲解 912. 排序数组 算法讲解 使用归并算法排序数组&#xff0c;我们先在数组中寻找一个mid点&#xff0c;然后把数组分成了两部分&#xff0c;我们先排左部分&#xff0c;排左边部分的时候有需要将当前的子数组分成两部分&#xff0c;继续循环&#xff0c;直到当前子数组…

嵌入式物联网开发基石

前言 嵌入式物联网开发工程师&#xff0c;是一个蓝海新风口&#xff0c;高薪不内卷的行业。需要掌握熟悉c/c、单片机、数据结构&#xff0c;linux系统&#xff0c;ARM单片机&#xff0c;嵌入式。可以从事工作项目loT、视频监控领域的产品&#xff0c;跨平台系统&#xff0c;智能…

linux内核初始化成功后是如何过渡到android初始化的

Android用的linux内核&#xff0c;以完成OS该有的功能&#xff0c;例如&#xff0c;文件系统&#xff0c;网络&#xff0c;内存管理&#xff0c;进程调度&#xff0c;驱动等 &#xff0c;向下管理硬件资源向上提供系统调用。另一些Android特有驱动也放在内核之中。 当linux内核…

时序分析基础(6)——input delay时序分析

1 简介 FPGA对于外部的时钟以及数据的延时信息是不知道的&#xff0c;在低速时钟且时钟发射沿在数据正中心的时候&#xff0c;一般可以不做约束来直接使用。但是到了高速时钟或者双沿采样或者发射沿和数据对齐的情况下&#xff0c;这时候就需要告诉VIVADO外部的时钟与数据情况来…

前端css中border-radius的简单使用

前端css中border-radius的使用 一、前言二、border-radius语法三、border-radius的模型例子1.源码12.源码1效果截图 四、border-radius的动画效果&#xff08;动态交互&#xff09;1.源码22.源码2显示效果 五、结语六、定位日期 一、前言 在CSS中&#xff0c;我们常用border-r…

LE Audio —— Boardcast 与 Unicast 对比

蓝牙低功耗音频&#xff08;Bluetooth LE Audio&#xff0c;简称&#xff1a;LE Audio&#xff09;自 2020 年 1月 发布&#xff0c;到 2022 年 7 月完成全套规范的定义。 LE Audio是新一代蓝牙音频技术标准&#xff0c;采用了全新的音频架构&#xff0c;充分利用低功耗蓝牙无…

二维码门楼牌管理应用平台建设:网格化管理的新篇章

文章目录 前言一、二维码门楼牌管理应用平台的建设背景二、二维码门楼牌管理应用平台的功能特点三、二维码门楼牌管理应用平台的实际应用四、二维码门楼牌管理应用平台的前景展望 前言 随着信息技术的飞速发展&#xff0c;二维码门楼牌管理应用平台的建设已成为城市网格化管理…

【Java基础】25.包(package)

文章目录 前言一、包的作用二、创建包三、import 关键字四、package 的目录结构五、设置 CLASSPATH 系统变量 前言 为了更好地组织类&#xff0c;Java 提供了包机制&#xff0c;用于区别类名的命名空间。 一、包的作用 把功能相似或相关的类或接口组织在同一个包中&#xff…

STM32 HAL库 利用CH376进行USB文件读写

STM32 其实可以进行读取USB文件,但仅限于F4以上芯片才可以进行SUB文件读写,但在项目开发中,往往用不到此芯片,那么只能通过外挂的USB芯片进行USB文件读写,本文则是采用STM32F103的SPI与CH376进行通信,通过CH376操作指令进行操作。 1、CH376介绍 CH376芯片 是沁恒的一款文…

Modelsim自动化仿真脚本(TCL)——简单实例

目录 1. Modelsim与TCL脚本的关系 2.实验文件 2.1设计文件 2.2仿真测试文件 2.3. 脚本文件 3. 实验步骤 3.1. 创建文件夹 3.2. 指定路径 3.3. 创建工程 3.4. 运行命令 3.4. 实验效果 1. Modelsim与TCL脚本的关系 TCL&#xff08;Tool Command Language&#xff09;是…

Vue.js------Vue组件基础

能够理解Vue组件概念和作用能够掌握封装创建组件能力能够使用组件之间通信能够完成todo案例 一.Vue组件创建和使用 1.折叠面板-实现多个 创建一个文件夹demo 具体步骤请参考vue.js---vue基础 ⚫ 解决方案: 采用vue提供的单.vue文件-组件方式来封装一套然后复用 在component…