【LeetCode】--- 动态规划 集训(二)

目录

  • 一、63. 不同路径 II
    • 1.1 题目解析
    • 1.2 状态转移方程
    • 1.3 解题代码
  • 二、931. 下降路径最小和
    • 2.1 题目解析
    • 2.2 状态转移方程
    • 2.3 解题代码
    • 三、174. 地下城游戏
    • 3.1 题目解析
    • 3.2 状态转移方程
    • 3.3 解题代码

一、63. 不同路径 II

题目地址: 不同路径 II


一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
在这里插入图片描述
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

1.1 题目解析

状态表示:

对于这种「路径类」的问题,我们的状态表示⼀般有两种形式

  1. [i, j]位置出发,到达⽬标位置有多少种方式;
  2. 从起始位置出发,到达 [i, j]位置,,⼀共有多少种方式。

这⾥选择第⼆种定义状态表示的方式:dp[i][j]表示:走到 [i, j]位置处,⼀共有多少种方式。

状态转移:

简单分析⼀下。如果 dp[i][j]表示到达 [i, j]位置的方法数,那么到达 [i, j]位置之前的⼀小步,有两种情况:

  1. [i, j]位置的上方( [i - 1, j]的位置)向下走一步,转移到 [i, j]位置;
  2. [i, j]位置的左方( [i, j - 1]的位置)向右⾛⼀步,转移到 [i, j]位置。

但是, [i - 1, j][i, j - 1]位置都是可能有障碍的,此时从上⾯或者左边是不可能到达 [i, j]位置的,也就是说,此时的方法数应该是 0。由此我们可以得出⼀个结论,只要这个位置上「有障碍物」,那么我们就不需要计算这个位置上的值,直接让它等于 0 即可。

初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

  1. 辅助结点⾥⾯的值要「保证后续填表是正确的」;
  2. 下标的映射关系」。

在本题中,添加一行,并且添加⼀列后,只需将 dp[1][0]的位置初始化为 1即可。 添加一行和一列是因为[i, j]位置需要[i - 1, j][i, j - 1]两个方位的值,以此确保状态表不越界,将dp[1][0]初始化为1,为了保证在起点时有方法数 1 。

填表顺序:

根据「状态转移」的推导,填表的顺序就是 「从上往下」填每一行,每一行「从左往右」。

返回值:

根据「状态表⽰」,我们要返回的结果是 dp[m][n]

1.2 状态转移方程

从当前位置的上方和左方,到达当前位置的方法数:dp[i][j] = dp[i - 1][j] + dp[i][j - 1];。还需要注意的是若当前位置为障碍物,直接将当前状态置为0(即dp[i][j] = 0;)

1.3 解题代码

class Solution 
{
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) 
    {
        int row = obstacleGrid.size(), col = obstacleGrid[0].size();
        //1. 创建dp表
        vector<vector<int>> dp(row + 1, vector<int>(col + 1));
        //2. 初始化
        dp[0][1] = 1;
        //3. 填表
        //从上到下填表 -> 从左到右填表
        for (int i = 1; i <= row; ++i)
            for (int j = 1; j <= col; ++j)
                if(obstacleGrid[i - 1][j - 1] == 0) dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
        //4. 返回值
        return dp[row][col];
    }
};

二、931. 下降路径最小和

题目地址: 931. 下降路径最小和


给你一个n x n方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径最小和
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置(row, col) 的下一个元素应当是(row + 1, col - 1)(row + 1, col)或者 (row + 1, col + 1)

在这里插入图片描述
输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:如图所示,为和最小的两条下降路径

2.1 题目解析

状态表示:

对于这种「路径类」的问题,我们的状态表示一般有两种形式:

  1. 从 [i, j] 位置出发,到达⽬标位置有多少种方式;
  2. 从起始位置出发,到达 [i, j] 位置,⼀共有多少种方式

这里选择第⼆种定义状态表示的方式:
dp[i][j]表示:到达 [i, j]位置时,所有下降路径中的最小和。

状态转移:

对于普遍位置 [i, j],根据题意得,到达 [i, j]位置可能有三种情况

  1. 从正上方 [i - 1, j]位置转移到 [i, j]位置;
  2. 从左上方 [i - 1, j - 1]位置转移到 [i, j]位置;
  3. 从右上方 [i - 1, j + 1]位置转移到 [i, j]位置;

我们要的是三种情况下的「最小值」,然后再加上矩阵在 [i, j]位置的值。于是 dp[i][j] = min(dp[i - 1][j], min(dp[i - 1][j - 1], dp[i - 1][j + 1])) + matrix[i][j]

初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:

  1. 辅助结点里面的值要「保证后续填表是正确的」;
  2. 下标的映射关系」。

事实上这题的状态转移方程是不难想到的,而关键问题在于初始化。[i, j]位置的值可以从三个方向来 - 上、左、右,所以为了不越界,在本题中,需要「加上一行」,并且「加上两列」(最左边和最右边)。 所有的位置都初始化为无穷大,然后将第一行初始化为 0 即可。将初始值设为无穷大是因为,这些是虚拟节点,不应该对选数造成影响(即大于其他两路的值)。

填表顺序:

根据「状态表示」,填表的顺序是「从上往下」。

返回值:

注意这里不是返回 dp[m][n]的值!
题⽬要求「只要到达最后一行」就行了,因此这⾥应该返回「 dp 表中最后一行的最小值」。

2.2 状态转移方程

dp[i][j] = min(dp[i - 1][j], min(dp[i - 1][j - 1], dp[i - 1][j + 1])) + matrix[i][j];

2.3 解题代码

class Solution 
{
public:
    int minFallingPathSum(vector<vector<int>>& matrix) 
    {
        //1. 创建dp表
        int row = matrix.size(), col = matrix.size();
        vector<vector<int>> dp(row + 1, vector<int>(col + 2, INT_MAX));
        //2. 初始化列表
        //第一行变为0
        for(int i = 0; i < col + 2; ++i) dp[0][i] = 0;
        //3. 填表
        for(int i = 1; i <= row; ++i)
            for(int j = 1; j <= col; ++j)
                dp[i][j] = matrix[i - 1][j - 1] 
                             + min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i - 1][j + 1]);
        //4. 返回结果
        int ans = INT_MAX;
        for(int i = 1; i <= col; ++i)
            ans = min(ans, dp[row][i]);
        return ans;
    }
};

三、174. 地下城游戏

题目地址: 174. 地下城游戏


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

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快解救公主,骑士决定每次只 向右向下 移动一步。
返回确保骑士能够拯救到公主所需的最低初始健康点数。
注意: 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

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

3.1 题目解析

状态表示:

这道题如果我们定义成:从起点开始,到达 [i, j]位置的时候,所需的最低初始健康点数。那么我们分析状态转移的时候会有⼀个问题:那就是我们当前的健康点数还会受到后面的路径的影响。也就是从上往下的状态转移不能很好地解决问题。

这个时候我们要换⼀种状态表示:[i, j]位置出发,到达终点时所需要的最低初始健康点数。 这样我们在分析状态转移的时候,后续的最佳状态就已经知晓。

综上所述,定义状态表示为:
dp[i][j]表示:从 [i, j]位置出发,到达终点时所需的最低初始健康点数。

状态转移方程:

对于 dp[i][j],从 [i, j]位置出发,下⼀步会有两种选择(为了方便理解,设 dp[i][j]的最终答案是 x ):

  1. 走到右边,然后走向终点:
    那么我们在 [i, j]位置的最低健康点数加上这⼀个位置的消耗,应该要⼤于等于右边位置的最低健康点数,也就是: x + dungeon[i][j] >= dp[i][j + 1]。通过移项可得: x >= dp[i][j + 1] - dungeon[i][j]。因为我们要的是最小值,因此这种情况下的 x = dp[i][j + 1] - dungeon[i][j]
  2. 走到下边,然后走向终点:
    那么我们在 [i, j]位置的最低健康点数加上这⼀个位置的消耗,应该要大于等于下边位置的最低健康点数,也就是: x + dungeon[i][j] >= dp[i + 1][j]。通过移项可得: x >= dp[i + 1][j] - dungeon[i][j]。因为我们要的是最小值,因此这种情况下的 x = dp[i + 1][j] - dungeon[i][j]

综上所述,我们需要的是两种情况下的最小值,因此可得状态转移方程为:dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]

但是,如果当前位置的 dungeon[i][j]是⼀个比较大的正数的话, dp[i][j]的值可能变成 0 或者负数。也就是最低点数会小于 1 ,那么骑士就会死亡。因此我们求出来的 dp[i][j]如果小于等于 0 的话,说明此时的最低初始值应该为 1 。处理这种情况仅需让 dp[i][j]与 1 取⼀个最大值即可:dp[i][j] = max(1, dp[i][j])

初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:

  1. 辅助结点⾥⾯的值要「保证后续填表是正确的」;
  2. 「下标的映射关系」。

在本题中,在 dp 表最后⾯添加一行,并且添加⼀列后,所有的值都先初始化为⽆穷大,然后让dp[m][n - 1] = dp[m - 1][n] = 1即可。

填表顺序:

根据「状态转移方程」,我们需要「从下往上填每一行」,「每一行从右往左」

返回值:

根据「状态表示」,我们需要返回 dp[0][0]的值。

3.2 状态转移方程

骑士到达当前位置最低健康点数:dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]
确保健康值不低于1:dp[i][j] = max(1, dp[i][j]);

3.3 解题代码

class Solution
{
    public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) 
    {
        int m = dungeon.size(), n = dungeon[0].size();
        // 建表 + 初始化
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
        dp[m][n - 1] = dp[m - 1][n] = 1;
        // 填表
        for(int i = m - 1; i >= 0; i--)
        {
            for(int j = n - 1; j >= 0; j--)
            {
                dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j];
                dp[i][j] = max(1, dp[i][j]);
            }
        }
        // 返回结果
        return dp[0][0];
    }
};

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

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

相关文章

机器学习数据预处理—统计分析方法

数据预处理 1 数据规范化 量纲&#xff0c;指将一个物理导出量用若干基本量的乘方之积表示出来的表达式。数据的比较需要关注两点——绝对数值和量纲&#xff0c;而特征间因为量纲的存在导致无法直接通过绝对数值比较大小&#xff0c;也就无法判断特征间的重要性。例如若某个…

中霖教育:注册会计师专业阶段考试难吗?

注册会计师专业阶段考试包含的考试科目众多&#xff0c;涉及的专业领域广泛&#xff0c;所以整体的难度相对较高。根据历年统计数据&#xff0c;该阶段的通过率平均约为22%&#xff0c;综合阶段的通过率在72%。 针对专业阶段&#xff0c;具体包括以下六门科目&#xff1a;《会…

DC9 Debian和sql注入

信息收集 sudo arp-scan -l 列出局域网主机 arp-scan向局域网中所有可能的ip地址发出arp请求包&#xff0c;如果得到arp回应&#xff0c;就证明局域网中某台主机使用了该ip dc9的ip &#xff1a; 192.168.146.133 访问网页 cms为Debian 端口扫描 22端口是filtered 隐藏目…

C++ 内存分配时地址对齐

如果数据地址的对齐与CPU相兼容&#xff0c;那么CPU读写内存时性能会更高。 因此在C中&#xff0c;有时会希望在堆或栈中分配内存时&#xff0c;返回的地址能按照特定的长度对齐。 如果希望在栈中分配的内存时&#xff0c;返回地址按照特定长度对齐&#xff0c;可以使用 alig…

Vue+OpenLayers7入门到实战:OpenLayers如何销毁已经创建好的地图容器

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上如何销毁已经创建好的地图容器。 在某些场景下,可能会需要销毁之前的地图,重新创建新的地图的需要,因此本章介绍一下在开始创建地图前如何先销毁之前的地图的功能。…

sharo反序列化漏洞

启动docker 服务 sudo service docker start 打开靶场 sudo docker run -d -p 80:8080 medicean/vulapps:s_shiro_1 输入docker虚拟机地址打开靶机sharo框架 如何利用漏洞 打开工具目录在终端中打开 输入靶机地址 打开yaki监听端口可以设置为6666 返回工具填写靶机ip和端口 …

【二分查找】Leetcode 山脉数组的峰顶索引

题目解析 852. 山脉数组的峰顶索引 这到题使用暴力枚举的查找方法发现这段数组是有二段性的&#xff0c;峰顶左边的一段区间是一段递增区间&#xff0c;右边的一段区间是一段递减区间 算法讲解 class Solution { public:int peakIndexInMountainArray(vector<int>&am…

微信小程序备案指南及注意事项

如何备案小程序&#xff1f; 原文可参考&#xff1a; 微信小程序备案指南及注意事项 注意&#xff1a;备案需要提前准备好以下材料&#xff1b; 身份证正反面照片&#xff08;必须&#xff09;&#xff1b;营业执照照片&#xff08;非个人主体需要&#xff09;&#xff1b; 一…

C#学习笔记8:接口、委托、事件

今日继续我的C#学习之路&#xff0c;今日学习接口、委托、事件&#xff0c;文章从实践出发学习这三个设计理念&#xff0c;并提供完整源码 目录 1、接口(多重继承)&#xff1a; 代码&#xff1a; 运行结果&#xff1a; 2、委托&#xff08;方法的代理/函数指针&#xff09;&…

一款轻量、干净的 Vue 后台管理框架

开始之前 在开始介绍之前我想谈谈为什么要自己做一个后台管理&#xff0c;我知道很多人都用一些开源的后台管理项目&#xff0c;这些老前辈有很多亮点值得学习&#xff0c;但是存在的一些问题同样不可忽视&#xff0c;我认为很多开发者会被困扰(仅代表个人观点) 技术栈老旧不升…

C语言进阶课程学习记录-第24课 - #pragma 使用分析

C语言进阶课程学习记录-第24课 - #pragma 使用分析 #pragma实验-#pragma messagecmd窗口运行 实验-pragma oncebcc编译报错gcc编译成功global.h代码优化 #pragma pack实验BCC编译器输出 小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程…

特征提取算法

特征提取算法 0. 写在前边1. Harris算法1.1 写在前面1.2 Harris算法的本质1.3 Harris算法的简化 2. Harris3D2.1 Harris3D算法问题定义2.2 Harris3D with intensity2.3 Harris3D without intensity 3. ISS特征点的应用 0. 写在前边 本篇将介绍几种特征提取算法&#xff0c;特征…

服务器数据恢复—EqualLogic PS6100系列存储数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌EqualLogic PS6100系列存储阵列是一款容错功能较强的存储设备&#xff0c;具有较高的安全性能。一些硬件故障或者误操作也会破坏该系列存储内的数据&#xff0c;下面分享一个北亚企安数据恢复工程师接到的一个关于EQ PS6100存储的数据恢复…

java学习之线程池

java线程池优点&#xff1a; 降低线程创建和销毁的开销&#xff0c;提高系统性能。 提高线程的利用率和系统的吞吐量。 统一线程的管理和监控&#xff0c;避免线程泄漏和线程安全问题。 支持任务队列和拒绝策略等机制&#xff0c;提供灵活的任务调度和任务处理能力。 并不…

【网络安全之渗透测试】公开课-即将开始

​4月13日&#xff0c;下午一点&#xff0c;网络安全-渗透公开课&#xff0c;感兴趣的可以留言免费参加 &#x1f525;&#x1f525;&#x1f525;大新闻来啦&#xff01;各位网络安全爱好者、IT从业者、或者是想要保护自己的数字生活的小伙伴们&#xff0c;注意了&#xff01;…

利驰软件荣获2023年度中国智能制造优秀供应商及优秀推荐产品奖

2024年3月28-29日&#xff0c;由e-works数字化企业网主办的“第十三届中国智能制造高峰论坛暨第二十一届中国智能制造岁末盘点颁奖典礼”于北京西南华邑酒店召开。 大会邀请了中国工程院院士、阿里研究院副院长、国家智能制造专家委员会委员等行业专家&#xff0c;以及国内外知…

银行ITSS体系下低代码运维体系实践分享

前言 自2021年中国人民银行发布《金融科技发展规划&#xff08;2022-2025年&#xff09;》以来&#xff0c;商业银行迈入数字化转型的高阶阶段。在此背景下&#xff0c;为了进一步提高金融科技的管理水平&#xff0c;商业银行需要改变传统金融运维模式&#xff0c;对已有运维体…

Web漏洞-文件上传常见验证

后缀名&#xff1a;类型&#xff0c;文件头等 后缀名&#xff1a;黑白名单 文件类型&#xff1a;MIME信息 文件头&#xff1a;内容头信息 常见黑名单&#xff08;明确不允许上传的格式后缀&#xff09;&#xff1a;asp、php、jsp、aspx、cgi、war &#xff08;如果没有完整…

Retrofit2 完全解析 探索与okhttp之间的关系

//用于访问zhy的信息 http://192.168.1.102:8080/springmvc_users/user/zhy //用于访问lmj的信息 http://192.168.1.102:8080/springmvc_users/user/lmj 即通过不同的username访问不同用户的信息&#xff0c;返回数据为json字符串。 那么可以通过retrofit提供的PATH注解非…

Java文件流操作

一、文件创建和删除 public static void main(String[] args) throws IOException {File file new File("..\\hello-world.txt");//..表示在上机目录下创建hello-world.txtSystem.out.println(file.getPath());//返回当前相对路径System.out.println(file.getCanoni…