动态规划之地下城游戏

简介: 动态规划之地下城游戏

1. 题目分析



题目链接选自力扣 : 地下城游戏

image.png一开始看到这么多文字, 头都大了. 下面就来根据示例的图帮助理解一下

image.png

骑士每次只能向右或者向下走一步, 我们可以倒推一下示例的路线, 很容易就可以知道

image.png


2. 状态表示



按照我们往常的经验, 肯定是以 ( i, j ) 位置为结尾, 表示从起点到 ( i, j ) 位置时的所需的初始最低血量. 现根据这个状态表示我们来推导一下状态转移方程

还是根据最近的一步来进行划分.

image.png


还是一样, 求解到达 ( i, j ) 位置时的最低初始血量需要先求解到达 ( i, j-1 ) 的最低初始血量 dp[i, j-1] 和 到达 ( i-1, j ) 时的最低初始血量.


但是这有一个问题, 到达 ( i, j ) 位置的初始血量不仅仅受到 ( i, j-1 ) 位置和 ( i-1, j ) 位置的影响. 什么意思呢 ? 还是结合示例 1 来讲解 :

image.png


终点的最低血量不仅仅搜到画红圈的 1 和 30 的两个位置的最低血量影响. 还受到画绿色圈的影响. 它是有很多条不同路径的. 进入任何一个房间击败不了恶魔都需要重设起始点的初始血量. 因此此时的状态表示以某个位置结尾时从起始点到达该位置的最低初始血量就无法推导出状态转移方程了. 因为它收到的影响不仅仅是两个方向的.


那么, 这种表示方法行不通, 我们还有一种经验方法, 也就是以 ( i, j ) 位置为起点, 表示从 ( i, j ) 位置开始到达终点时所需要的最低初始血量.


3. 状态转移方程



以 i, j ) 位置为起点, 表示从 ( i, j ) 位置开始到达终点时所需要的最低初始血量. 推导此时的状态转移方程 dp[i][j] : 还是以最近的一步来划分问题

image.png


  1. 从 ( i, j ) 位置出发往 ( i, j+1 ) 位置走


什么时候才能从 ( i, j ) 位置正确进入到 ( i, j + 1 ) 位置呢 ? 假设 ( i, j ) 位置此时最低初始血量为 X, 当 X + dungeon[i][j] >= dp[i][j+1] 时才能有机会到达终点. 题目要求最低初始血量, 取等时即可


dp 表里存的是从某个位置出发时的最低血量. 此时这个位置的最低血量为 X, 想要走出这个位置就需要先击败里面的恶魔 dungeon[i][j], 之后剩余血量 X + dungeon[i][j] 也就是此时进入 ( i, j+1 ) 的最低初始血量必须要大于等于 ( i, j+1 ) 的最低初始血量 dp[i][j+1] 才能进入, 否则无法到达终点.


此时 X = dp[i][j+1] - dungeon[i][j]


  1. 从 ( i, j ) 位置出发往 ( i+1, j ) 位置走


同理, 从 ( i, j ) 位置出发往 ( i+1, j ) 想要到达终点所需要的最低初始血量为 dp[i+1][j] - dungeon[i][j]


由于我们求的是从某点出发到终点的最低初始血量, 最终 dp[i][j] = Math.min(dp[i][j+1], dp[i+1][j] ) - dungeon[i][j]


PS :有一点是需要注意的, 当 dungeon[i][j] 非常大的时候, dp[i][j] 最终是一个小于 0 的数. 这是什么意思 ?


也就是当你进入 ( i, j ) 位置时, 这里面是一个非常大的血包 dungeon[i][j] 此时我进入 ( i, j ) 位置时自身血量哪怕是负数最终也可以进入到 ( i, j+1 ) 位置. 这是不合理的, 负数就应该直接死亡. 必须保证在进入 ( i, j ) 位置时它的血量大于 0 才行.


因此我们需要对最终的 dp[i][j] 进行处理. dp[i][j] = Math.max( 1, dp[i][j] ). 也就是进入某处时的最低初始血量必须是大于 0 才能进入 !


4. 初始化



根据状态转移方程, 想要计算从 ( i, j ) 位置出发到达终点时的最低初始血量, 就需要先计算它的前一个位置和下一个位置的最低初始血量. 因此以下位置在填表时不进行初始化就会导致填表发生越界错误.

image.png

因此, 这些位置在进行填表之前, 都是我们需要进行初始化的. 同样, 我们还是采取新增一行一列的办法, 将初始化的内容放在填表当中. 但此时不是和之前一样, 左边添加一列上面添加一行, 而是下面添加一行最后面添加一列

image.png


  1. 当填写这个位置的值时

由于它受到向右和向下两个位置的最小初始血量影响. 但是这两个位置是我们新开的. 它不应该影响我们要填的这个位置的值.


如果要填的这个红星的位置值为 0 的话, 显然是不对的. 这意味着你从这个地方出发的最低初始血量是 0, 而我们上面说了最低初始血量至少是 1. 而我们想要向右走或者向左走, 进去的最低血量就是 1, 可以理解为救完人后, 出来的最低血量至少是 1. 因此这两个位置的初始化结果为 1

image.png


  1. 填写其他位置的值时

当初始化其他位置的时候,这个时候要填写的位置除了受到新增位置的影响还受到原本矩阵内容的影响. 而我们新增的这些位置不能影响原本的数据. 根据状态转移方程取得是这两个方向上值的最小值.因此新增的位置的值初始化为无穷大就不会影响我们填写的最终结果了.

ac17b8bf51212504cefe78aaecc21816.png


5. 填表顺序



可以看到, 填写 ( i, j ) 位置时, 需要依赖它的后一个和下一个位置的值. 因此我们填表的顺序为 从下到上填写每一行, 而每一行又从右往左填写


6. 返回值



题目要求返回到达 ( m, n ) 位置时的最低初始血量. 而我们的状态表示为从某个位置开始到达终点时所需要的最低初始血量. 也就是 dp[0][0].


7. 代码演示



class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        // 1. 创建 dp 表
        int m = dungeon.length;
        int n = dungeon[0].length;
        int[][] dp = new int[m + 1][n + 1];
        // 2. 初始化 
        // 将最后一行初始化为无穷大
        for(int j = 0; j <= n; j++) {
            dp[m][j] = Integer.MAX_VALUE;
        }
        // 将最后一列初始化为无穷大
        for(int i = 0; i <= m; i++) {
            dp[i][n] = Integer.MAX_VALUE;
        }
        // 将终点的右边和下边位置初始化为 1
        dp[m][n - 1] = dp[m - 1][n] = 1;
        // 3. 填写 dp 表, 从下往上每一行, 每一行从右往左
        for(int i = m - 1; i >= 0; i--) {
            for(int j = n - 1; j >= 0; j--) {
                // 根据状态转移方程填写
                // 虽然多开了一行一列, 但是对应到原本的 dungeon 的位置没变. 
                // 因为添加的位置是在最后一行和最后一列.
                dp[i][j] = Math.min(dp[i][j + 1], dp[i + 1][j] ) - dungeon[i][j];
                // 如果最终的结果太小为负数, dp 表中表示某个位置为起点的最低初始血量至少大于 1
                // 因此要对这个最终结果进行处理
                dp[i][j] = Math.max(1, dp[i][j]);
            }
        }
        // 4. 确认返回值
        return dp[0][0];
    }
}


相关文章
|
算法
经典控制算法——PID算法原理分析及优化
这篇文章介绍了PID控制算法,这是一种广泛应用的控制策略,具有简单、鲁棒性强的特点。PID通过比例、积分和微分三个部分调整控制量,以减少系统误差。文章提到了在大学智能汽车竞赛中的应用,并详细解释了PID的基本原理和数学表达式。接着,讨论了数字PID的实现,包括位置式、增量式和步进式,以及它们各自的优缺点。最后,文章介绍了PID的优化方法,如积分饱和处理和微分项优化,以及串级PID在电机控制中的应用。整个内容旨在帮助读者理解PID控制的原理和实际运用。
2012 1
蓝牙核心规范(V5.3)-深入详解之SCO和eSCO的异同
蓝牙核心规范(V5.3)-深入详解之SCO和eSCO的异同
2691 0
蓝牙核心规范(V5.3)-深入详解之SCO和eSCO的异同
|
11月前
|
机器学习/深度学习 搜索推荐 API
淘宝/天猫按图搜索(拍立淘)API的深度解析与应用实践
在数字化时代,电商行业迅速发展,个性化、便捷性和高效性成为消费者新需求。淘宝/天猫推出的拍立淘API,利用图像识别技术,提供精准的购物搜索体验。本文深入探讨其原理、优势、应用场景及实现方法,助力电商技术和用户体验提升。
|
移动开发 小程序 API
uniapp中uview组件库的丰富Upload 上传上午用法
uniapp中uview组件库的丰富Upload 上传上午用法
731 0
|
缓存 监控 Linux
Linux配置成代理服务器
代理服务器(Proxy Server)是一种位于计算机网络中的中间服务器,它充当了客户端和目标服务器之间的中介,用于转发客户端请求并获取目标服务器的响应。代理服务器的主要功能包括以下几点:
7430 1
|
JavaScript 前端开发
react18【系列实用教程】双向绑定表单 (2024最新版)含受控组件、非受控组件、单行多行输入框 input,下拉选择 select,单选 radio,多选 checkbox,标签 label
react18【系列实用教程】双向绑定表单 (2024最新版)含受控组件、非受控组件、单行多行输入框 input,下拉选择 select,单选 radio,多选 checkbox,标签 label
315 1
通义大模型使用指南之通义听悟
本文介绍了阿里云通义平台的注册和使用,主要包括两个部分:注册和功能介绍。用户可以通过访问网址 &lt;https://tongyi.aliyun.com/&gt; 进行注册。在功能介绍中,重点讲解了通义听悟的功能,它提供实时语音转文字、音视频文件转文字、智能总结和中英互译服务。用户可以体验实时录音并标记重点、问题和代办事项,方便会议记录和整理。此外,通义听悟还支持上传音视频文件转写和播客链接转写,以及浏览器插件用于处理网页、手机和微信上的语音内容。
2464 0
Cannot download repomd.xml解决CentOS8 yum安装AppStream报错
Cannot download repomd.xml解决CentOS8 yum安装AppStream报错
1021 0
|
缓存 关系型数据库 MySQL
MySQL登录时出现Access denied for user ‘root‘@‘localhost‘ (using password: YES)无法打开的解决方法
MySQL登录时出现Access denied for user ‘root‘@‘localhost‘ (using password: YES)无法打开的解决方法
24212 0
|
缓存 前端开发 JavaScript
前端配置的 404问题
前端配置的 404问题
683 0