每日一题:地下城游戏

恶魔们抓住了公主并将她关在了地下城 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.mfbz.cn/a/581067.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ThreeJs 环境配置及遇到问题的解决方法

一、环境搭建 ThreeJs在实际在实际使用中更多的是结合框架开发例如&#xff1a;vue框架、react框架&#xff0c;在使用时需要配置开发环境&#xff0c;本文使用的是vscode ThreeJs NodeJs vue 1、ThreeJs安装 下载路径&#xff1a;GitHub - mrdoob/three.js: JavaScript…

CentOS命令大全:掌握关键命令及其精妙用法!

CentOS是一种流行的开源企业级Linux发行版&#xff0c;它基于Red Hat Enterprise Linux (RHEL)的源代码构建。对于系统管理员和运维工程师来说&#xff0c;掌握CentOS的常用命令至关重要。 这些命令不仅可以帮助管理服务器&#xff0c;还可以进行故障排查、性能监控和安全加固等…

【idea】idea 中 git 分支多个提交合并一个提交到新的分支

一、方法原理讲解 我们在 dev 分支对不同的代码文件做了多次提交。现在我们想要把这些提交都合并到 test 分支。首先我们要明白四个 git 操作&#xff0c; commit&#xff1a;命令用于将你的代码变更保存到本地代码仓库中&#xff0c;它创建了一个新的提交&#xff08;commit…

Ubuntu编译安装MariaDB并进行初始化配置

Ubuntu编译安装MariaDB并进行初始化配置 1. 编译安装MariaDB2. 配置MariaDB3. Docker安装MariaDB 1. 编译安装MariaDB MariaDB官方安装文档&#xff1a;https://mariadb.com/kb/en/Build_Environment_Setup_for_Linux/    下载MariaDB源码&#xff1a;https://mariadb.org/ma…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口&#xff0c;并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…

TCP-模拟BS架构通信

简介 bs是通过浏览器进行访问的每次访问都会开启一个短期的socket用来访问服务器的资源 响应报文的格式 服务端 bs架构中的b是浏览器&#xff0c;不需要我们书写&#xff0c;我们只需要书写服务端即可 服务端 public class Server {public static void main(String[] args) {S…

[C++]22:C++11_part_one

C11 一.列表初始化&#xff1a;1.{}初始化&#xff1a;2.C11扩大了列表初始化的范围&#xff1a;3.std::initializer_list1.简单类型的列表初始化&#xff1a;2.复杂类型的列表初始化3.实现vector的列表初始化4.实现list的列表初始化&#xff1a;5.不支持列表初始化&#xff1a…

制作一个RISC-V的操作系统十六-系统调用

文章目录 用户态和内核态mstatus设置模式切换核心流程封装代码背景解释代码示例解析解释目的 用户态和内核态 mstatus设置 此时UIE设置为1和MPIE为1&#xff0c;MPP设置为0 代表当前权限允许UIE中断发生&#xff0c;并且在第一个mret后将权限恢复为用户态&#xff0c;同时MIE也…

易错知识点(学习过程中不断记录)

快捷键专区&#xff1a; 注释&#xff1a;ctrl/ ctrlshift/ 保存&#xff1a;ctrls 调试&#xff1a; 知识点专区&#xff1a; 1基本数据类型 基本数据类型有四类&#xff1a;整型、浮点型、字符型、布尔型&#xff08;Boolean&#xff09;&#xff0c; 分为八种&#xff…

UE5 GAS开发P40 周期性效果,持续治疗

Periodic Gameplay Effects周期性的游戏效果 它们在一段时间内以固定的间隔重复应用到目标上。这种效果通常用于表示持续性伤害、治疗或其他影响&#xff0c;例如中毒、灼烧或回复效果。 修改GE_CrystalHeal,在Period改为每0.1秒执行一次 假如同时有三个持续时间在进行,那么这…

STM32与OLED显示屏通信(四针脚和七阵脚)

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. 单片机调试 2. OLED简介 3. 接线 4. OLED驱动函数 4.1 四针脚版本 OLED.c OLED.h OLED_Font.h 4.2 七针脚版本 引脚连接 OLED.c OLED.h OLED_Font.h 5. 主函数 工程文件模板 1. 单片机…

linux下安装deepspeed

安装步骤 一开始安装deepspeed不可以使用pip直接进行安装。 这时我们需要利用git进行clone下载到本地&#xff1a; git clone https://github.com/microsoft/DeepSpeed.git 进入到deepspeed的安装目录下 cd /home/bingxing2/ailab/group/ai4agr/wzf/Tools/DeepSpeed 激活…

verilog 从入门到看得懂---matlab 自动生成verilog

matlab 的强大不用多说&#xff0c;以前经常用simulink 生成c&#xff0c;最近尝试用simulink进行了verilog的生成&#xff0c;方法也很简单。 一个简单的示例如下。 1&#xff0c;新建一个模型文件&#xff0c;并且根据需要进行模型搭建 2.配置HDL生成模块 3.点击 generation…

纯血鸿蒙APP实战开发——全局状态保留能力弹窗

全局状态保留能力弹窗 介绍 全局状态保留能力弹窗一种很常见的能力&#xff0c;能够保持状态&#xff0c;且支持全局控制显隐状态以及自定义布局。使用效果参考评论组件 效果图预览 使用说明 使用案例参考短视频案例 首先程序入口页对全局弹窗初始化&#xff0c;使用Globa…

Linux学习之路 -- 进程篇 -- 自定义shell的编写

前面介绍了进程程序替换的相关知识&#xff0c;接下来&#xff0c;我将介绍如何基于前面的知识&#xff0c;编写一个简单的shell&#xff0c;另外本文的所展示的shell可能仅供参考。 目录 <1>获取用户的输入和打印命令行提示符 <2>切割字符串 <3>执行这个…

qt-C++笔记之滑动条QSlider和QProgressBar进度条

qt-C笔记之滑动条QSlider和QProgressBar进度条 —— 2024-04-28 杭州 本例来自《Qt6 C开发指南》 文章目录 qt-C笔记之滑动条QSlider和QProgressBar进度条1.运行2.阅读笔记3.文件结构4.samp4_06.pro5.main.cpp6.widget.h7.widget.cpp8.widget.ui 1.运行 2.阅读笔记 3.文件结构…

智慧供热一站式热网平衡多功能集成系统

供热管理地域分散的现实&#xff0c;决定必须采用先进技术手段开发软件系统&#xff0c;使各管理单位互联互通。在多年技术积累的基础上&#xff0c;公司采用目前成熟而且领先的技术架构&#xff0c;研发了适用于多个组织机构集中式管理的供热管理软件。使管理在技术上不再受地…

经典的目标检测算法有哪些?

一、经典的目标检测算法有哪些&#xff1f; 目标检测算法根据其处理流程可以分为两大类&#xff1a;One-Stage&#xff08;单阶段&#xff09;算法和Two-Stage&#xff08;两阶段&#xff09;算法。以下是一些经典的目标检测算法&#xff1a; 单阶段算法: YOLO (You Only Loo…

Java集合框架-Collection-queue

目录 一、Deque二、ArrayDequeArrayDeque层次结构图ArrayDeque概述ArrayDeque底层数据结构ArrayDeque常用方法(简略) 三、PriorityQueuePriorityQueue层次结构图PriorityQueue概述PriorityQueue 底层数据结构PriorityQueue常用方法(详细) Java里有一个叫做Stack的类&#xff0c…

[tkinter实现]汉字笔顺小软件

软件简介 本软件旨在帮助小学生通过互动式学习掌握汉字的基本笔画和笔顺。软件采用Tkinter库构建&#xff0c;提供了一个用户友好的图形界面&#xff0c;适合小学生使用。 主要功能&#xff1a; 汉字展示&#xff1a;软件能够展示单个汉字&#xff0c;并以动画形式演示其标准…
最新文章