【动态规划算法】路径问题

什么是动态规划算法

动态规划(Dynamic Programming,简称 DP)是一种通过分解复杂问题为重叠子问题,并存储子问题的解以避免重复计算,从而高效求解具有特定性质(重叠子问题、最优子结构)问题的算法思想。

一、核心思想:“分解 + 复用”

动态规划的核心在于:
1.将原问题拆解为规模更小的子问题;
2.求解子问题后,将结果存储起来(记忆化),避免后续重复计算;
3.基于子问题的解,推导出原问题的解。
简单来说,就是 “用已知的子问题答案,解决未知的大问题”,本质是用空间换时间。

二、动态规划的 2 个关键前提

只有当问题满足以下两个条件时,才能用动态规划求解:

  1. 重叠子问题
    问题可以分解为多个重复出现的子问题。
    例如:计算斐波那契数列 f(n) = f(n-1) + f(n-2) 时,f(5) 依赖 f(4) 和 f(3),f(4) 又依赖 f(3) 和 f(2)——f(3) 就是被重复计算的重叠子问题。
  2. 最优子结构(Optimal Substructure)
    问题的最优解包含子问题的最优解。
    例如:求 “从 A 到 B 的最短路径” 时,若路径 A→C→B 是最优解,则 A→C 和 C→B 一定分别是 A 到 C、C 到 B 的最短路径(子问题的最优解)。

三、动态规划的 2 种实现方式

动态规划通常有两种实现思路,核心都是 “存储子问题的解”,只是顺序不同:

  1. 自顶向下(Top-Down):递归 + 记忆化
    思路:从原问题出发,递归分解为子问题,用备忘录(数组或哈希表) 存储已求解的子问题答案,避免重复计算。
  2. 自底向上(Bottom-Up):迭代 + DP 表
    思路:从最小的子问题开始,按顺序求解,用DP 表(数组) 记录子问题的解,逐步推导出原问题的解。

四、经典应用场景

动态规划广泛用于求解 “最优问题” 或 “计数问题”,典型案例包括:
计数问题:爬楼梯(方法数)、不同路径(网格中路径数);
最优问题:最长公共子序列(LCS)、最大子数组和、0-1 背包问题(最大价值);
其他:编辑距离(字符串相似度)、打家劫舍(不相邻房屋最大金额)等。

五、动态规划与其他算法的区别

与递归:递归可能重复计算子问题,动态规划通过记忆化避免重复,效率更高;
与贪心算法:贪心只做局部最优选择,不依赖子问题的解;动态规划则基于子问题的最优解推导全局最优;
与分治法:分治法(如归并排序)的子问题不重叠,无需存储解;动态规划的子问题重叠,必须存储解。

一. (62.)不同路径(力扣)

在这里插入图片描述

1.1算法原理

  1. 状态表⽰:

对于这种「路径类」的问题,我们的状态表⽰⼀般有两种形式:
i. 从 [i, j] 位置出发,巴拉巴拉;
ii. 从起始位置出发,到达 [i, j] 位置,巴拉巴拉。
这⾥选择第⼆种定义状态表⽰的⽅式:
dp[i][j] 表⽰:⾛到 [i, j] 位置处,⼀共有多少种⽅式。

  1. 状态转移⽅程:

从最近的一步将问题划分为子问题,再由若干个子问题来来解决总问题。dp[i][j] 表⽰到达 [i, j] 位置的⽅法数,到达 [i, j] 位置之前的⼀⼩步,有两种情况:
i. 从 [i, j] 位置的上⽅( [i - 1, j] 的位置)向下⾛⼀步,转移到 [i, j] 位置;
ii. 从 [i, j] 位置的左⽅( [i, j - 1] 的位置)向右⾛⼀步,转移到 [i, j] 位置。
由于我们要求的是有多少种⽅法,因此状态转移⽅程就呼之欲出了: dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 。

为什么方法数不将这一小步也加上?
因为这一小步不是方法数,而是一种方法中延申的一步,是路长步数,不影响方法数

3.初始化

通过添加虚拟节点来避免复杂边界问题讨论,需注意:
1.虚拟节点的值要保证后续填表是正确的
2.下标映射关系
在这里插入图片描述
在左上角第一个位置的上边或左边初始化为1,其他地方初始化为0,就不会影响后续填表

  1. 填表顺序:

根据状态转移⽅程的推导来看,填表的顺序就是从上往下填每⼀⾏,在填写每⼀⾏的时候从左往右。

  1. 返回值:

根据状态表⽰,由于多增加了一行一列的虚拟节点,我们要返回 dp[m][n] 的值(原本返回坐标为m-1,n-1)。

class Solution {
public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1));//初始化dp表的虚拟节点一行一列,将[0][1]或[1][0]位置赋1即可其他默认为0dp[1][0]=1;//填表for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m][n];}
};

二. (63.) 不同路径 II(力扣)

在这里插入图片描述

2.1算法原理

  1. 状态表示:

dp[i][j] 表⽰:⾛到 [i, j] 位置处,⼀共有多少种⽅式。

  1. 状态转移:

到达 [i, j] 位置之前的⼀⼩步,有两种情况:
i. 从 [i, j] 位置的上⽅( [i - 1, j] 的位置)向下⾛⼀步,转移到 [i, j] 位置;
ii. 从 [i, j] 位置的左⽅( [i, j - 1] 的位置)向右⾛⼀步,转移到 [i, j] 位置。
但是, [i - 1, j] 与 [i, j - 1] 位置都是可能有障碍的,此时从上⾯或者左边是不可能到达 [i, j] 位置的,也就是说,此时的⽅法数应该是 0。若dp[i][j]位置本身就有障碍物方法数直接为0
由此我们可以得出⼀个结论,只要这个位置上有障碍物,那么我们就不需要计算这个位置上的值,直接让它等于 0 即可。

3.初始化

通过添加虚拟节点来避免复杂边界问题讨论,需注意:
1.虚拟节点的值要保证后续填表是正确的
2.下标映射关系
在这里插入图片描述
在左上角第一个位置的上边或左边初始化为1,其他地方初始化为0,就不会影响后续填表

  1. 填表顺序:

根据状态转移⽅程的推导来看,填表的顺序就是从上往下填每⼀⾏,在填写每⼀⾏的时候从左往右。

  1. 返回值:

根据状态表⽰,由于多增加了一行一列的虚拟节点,我们要返回 dp[m][n] 的值(原本返回坐标为m-1,n-1)。

class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int n=obstacleGrid.size(),m=obstacleGrid[0].size();vector<vector<int>> dp(n+1,vector<int>(m+1));//初始化dp[0][1]=1;//填表for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(obstacleGrid[i-1][j-1]!=1)dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[n][m];}
};

三. 剑指 Offer 47. 礼物的最大价值(力扣)

在这里插入图片描述

3.1算法原理

1.状态表示:

dp[i][j] 表⽰:⾛到 [i, j] 位置处,此时的最⼤价值。

  1. 状态转移⽅程:

对于 dp[i][j] ,想要到达 [i, j] 位置,有两种⽅式:
i. 从 [i, j] 位置的上⽅ [i - 1, j] 位置,向下⾛⼀步,此时到达 [i, j] 位置能
拿到的礼物价值为 dp[i - 1][j] + grid[i][j] ;
ii. 从 [i, j] 位置的左边 [i, j - 1] 位置,向右⾛⼀步,此时到达 [i, j] 位置能
拿到的礼物价值为 dp[i][j - 1] + grid[i][j]
我们要的是最⼤值,因此状态转移⽅程为:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]

3.初始化

1.依旧是添加一行一列的虚拟节点,需注意的是与前两道题不同的是前两题求的是路径,本题求的是每个节点的“价值”,所以不能让虚拟节点影响到原本节点的价值,由状态转移方程可知,求一个节点的最大价值,总共有两种路径取大的一方,题目要求所有值都>=0,所以将所有虚拟节点初始化为0就没有影响
2.保证后续填表正确,注意dp表与原数组下标的映射关系

  1. 填表顺序:
    从上往下填写每⼀⾏,每⼀⾏从左往右。

5.返回值

由于多添加了一行一列的虚拟节点,返回 dp[m][n] 的值

class Solution {
public:int jewelleryValue(vector<vector<int>>& f) {int m=f.size(),n=f[0].size();vector<vector<int>> dp(m+1,vector<int>(n+1));//填表,初始化虚拟节点为0自动完成for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=max(dp[i-1][j],dp[i][j-1])+f[i-1][j-1];}}return dp[m][n];}
};

四. (931.) 下降路径最小和(力扣)

在这里插入图片描述

4.1算法原理

  1. 状态表⽰

dp[i][j] 表⽰:到达 [i, j] 位置时,所有下降路径中的最⼩和。

  1. 状态转移⽅程:

对于普遍位置 [i, j] ,根据题意得,到达 [i, j] 位置可能有三种情况:
i. 从正上⽅ [i - 1, j] 位置转移到 [i, j] 位置;
ii. 从左上⽅ [i - 1, j - 1] 位置转移到 [i, j] 位置;
iii. 从右上⽅ [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]

3.初始化

与前几道题增加一行一列的初始化不同,本题要增加两列一行,因为当前位置值受上一行左中右三个值的影响,所以原数组中最右边一列的值也面临初始化的问题,所以也需要增加虚拟节点
在这里插入图片描述
1.因为所求为下降路径的最小和,所以除第一行外的其他虚拟节点需要初始化为正无穷大,同时原数组第一行没有之前的下降路径,所以不能受虚拟节点的影响,第一行虚拟节点要设为0。
2.可以采取先将全部虚拟节点初始化为正无穷大然后将第一行改为0

  1. 填表顺序:

从上往下

  1. 返回值:

注意这⾥不是返回 dp[m][n] 的值!
题⽬要求只要到达最后⼀⾏就⾏了,因此这⾥应该返回 dp 表中最后⼀⾏的最⼩值

class Solution {
public:int minFallingPathSum(vector<vector<int>>& m) {int x=m.size(),y=m[0].size();vector<vector<int>> dp(x+1,vector<int>(y+2,INT_MAX));//初始化for(int j=0;j<=y+1;j++){dp[0][j]=0;}//填表for(int i=1;i<=x;i++){for(int j=1;j<=y;j++){dp[i][j]=min({dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]})+m[i-1][j-1];}}//找到最小返回值int ret=INT_MAX;for(int i=x,j=1;j<=y;j++){if(dp[i][j]<ret) ret=dp[i][j];}return ret;}
};

五. (64.) 最小路径和(力扣)

在这里插入图片描述

5.1算法原理

  1. 状态表⽰:

dp[i][j] 表⽰:到达 [i, j] 位置处,最⼩路径和是多少。

  1. 状态转移:

表⽰到达 到达 [i, j] 位置处的最⼩路径和,那么到达[i, j] 位置之前的⼀⼩步,有两种情况:
i. 从 [i - 1, j] 向下⾛⼀步,转移到 [i, j] 位置;
ii. 从 [i, j - 1] 向右⾛⼀步,转移到 [i, j] 位置。
由于到 [i, j] 位置两种情况,并且我们要找的是最⼩路径,因此只需要这两种情况下的最⼩值,再加上 [i, j] 位置上本⾝的值即可。
也就是: dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]

  1. 初始化:

在这里插入图片描述

添加⼀⾏,并且添加⼀列后,所有位置的值可以初始化为⽆穷⼤,然后让
dp[0][1] = dp[1][0] = 1 即可。(因为求的是最小值,不能让虚拟节点干扰)

  1. 填表顺序:从上往下填每⼀⾏,每⼀⾏从左往后
  2. 由于多加了一行一列的虚拟节点,返回dp[m][n]
class Solution {
public:int minPathSum(vector<vector<int>>& g) {int m=g.size(),n=g[0].size();//创建dp表顺便初始化vector<vector<int>> dp(m+1,vector<int>(n+1,INT_MAX));//特殊初始化值dp[1][0]=dp[0][1]=0;//填表for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=min(dp[i-1][j],dp[i][j-1])+g[i-1][j-1];}}return dp[m][n];}
};

六. (174.) 地下城游戏(力扣)

在这里插入图片描述

6.1算法原理

  1. 状态表⽰:

这道题如果我们定义成:从起点开始,到达 [i, j] 位置的时候,所需的最低初始健康点数。那么我们分析状态转移的时候会有⼀个问题:那就是我们当前的健康点数还会受到后⾯的路径的影响。也就是从上往下的状态转移不能很好地解决问题。
这个时候我们要换⼀种状态表⽰:从 [i, j] 位置出发,到达终点时所需要的最低初始健康点数。这样我们在分析状态转移的时候,后续的最佳状态就已经知晓。
综上所述,定义状态表⽰为:
dp[i][j] 表⽰:从 [i, j] 位置出发,到达终点时所需的最低初始健康点数

  1. 状态转移⽅程:

在这里插入图片描述
在 [i, j] 位置的最低健康点数加上这⼀个位置的消耗,应该要⼤于等于右边或下边位置的最低健康点数。(图中d代表dungeon)
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. 初始化:

由于节点状态表示与前面题不同,初始化方式也改变,每个节点依赖后一个下位置和右位置节点的值,在 dp 表最后⾯添加⼀⾏,并且添加⼀列后,所有的值都先初始化为⽆穷⼤(因为求最小值不能被虚拟节点影响),然后让dp[m][n - 1] = dp[m - 1][n] = 1 (终点房间所依赖的下一个位置,最小值为1根据题目要求,不然没法进行下去和其他虚拟节点不同)。

4.填表顺序:

发生变化,要从下往上填每⼀⾏,每⼀⾏从右往左

  1. 返回值:

根据状态表⽰,需要返回 dp[0][0] 的值

class Solution {
public:int calculateMinimumHP(vector<vector<int>>& d) {int m=d.size(),n=d[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])-d[i][j];dp[i][j]=max(1,dp[i][j]);//防止为负数}}return dp[0][0];}
};

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

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

相关文章

Java基本技术讲解

一、基础语法三要素 暂时无法在飞书文档外展示此内容 &#x1f511; 黄金法则​&#xff1a;每个变量都要声明类型&#xff01;二、程序逻辑控制&#xff08;游戏行为核心&#xff09; 条件判断&#xff1a;if-else - “岔路口选择” // 捡到金币逻辑 if (isTouching(Coin.clas…

【网络基础2】路由器的 “两扇门”:二层接口和三层接口到底有啥不一样?

目录 前言:路由器不是只有 “插网线的口” 一、先搞懂一个基础:路由器是 “网络交通枢纽” 二、二层接口:“小区内部的单元门”,只认 “住户身份证” 1. 啥是二层接口? 2. 用 “小区内部串门” 理解二层接口 步骤 1:手机打包数据,写上 “收件人身份证” 步骤 2:二…

MLIR TableGen

简介 TableGen 是一种领域特定语言&#xff08;DSL&#xff09;&#xff0c;TableGen 的设计目标是允许编写灵活的描述&#xff0c;并将记录的通用特性提取出来&#xff0c;从而减少重复代码并提高代码的可维护性。 TableGen的工作流程&#xff1a; 前端解析&#xff1a; Ta…

2、docker容器命令 | 信息查看

1、命令总览命令作用docker ps查看运行中的容器&#xff08;-a查看所有容器&#xff09;docker logs [CONTAINER]查看容器日志&#xff08;-f实时追踪日志&#xff09;docker inspect [CONTAINER]查看容器详细信息&#xff08;JSON格式&#xff09;docker stats [CONTAINER]实时…

【MySQL】MySQL中锁有哪些?

一、按照粒度分类&#xff1a; 粒度越小&#xff0c;并发度越高&#xff0c;锁开销越大。 1.全局锁&#xff1a; 作用&#xff1a; 锁定整个MySQL实例(所有数据库)。适用场景&#xff1a; 全库逻辑部分。(确保备份期间数据的一致性。)实现方式&#xff1a; 通过 FLUSH TABLES W…

语义分割--deeplabV3+

根据论文网络结构图讲一下&#xff1a;网络分为两部分&#xff1a;encoder和decoder部分。 Encoder&#xff1a;DCNN就是主干网络&#xff0c;例如resnet&#xff0c;Xception&#xff0c;MobileNet这些&#xff08;主干网络也要使用空洞卷积&#xff09;&#xff0c;对dcnn的结…

Azure DevOps 中的代理

必知词汇 深入研究 Azure DevOps 中的代理之前需要掌握的基本概念: 代理:Azure DevOps 中的代理是一个软件组件,负责执行流水线中的任务和作业。这可能包括数据中心内的物理服务器、本地或云端托管的虚拟机,甚至是容器化环境。这些代理可以在各种操作系统和环境中运行,例如…

AUTOSAR进阶图解==>AUTOSAR_SRS_ADCDriver

AUTOSAR ADC驱动详解 基于AUTOSAR标准的ADC驱动模块需求规范分析目录 ADC驱动模块概述 关键概念定义 ADC驱动架构 ADC驱动在AUTOSAR分层架构中的位置ADC驱动的主要职责 ADC驱动配置结构 通用配置(AdcGeneral)硬件单元配置(AdcHwUnit)通道配置(AdcChannel)通道组配置(AdcChanne…

宝马集团与SAP联合打造生产物流数字化新标杆

在德国雷根斯堡的宝马工厂&#xff0c;每57秒就有一辆新车下线。这座工厂不仅是汽车制造的基地&#xff0c;更是宝马集团向SAP S/4HANA云平台转型的先锋项目。通过“RISE with SAP”计划&#xff0c;宝马将该工厂的运营系统全面迁移至SAP S/4HANA Cloud Private Edition&#x…

Go 语言实战:构建一个高性能的 MySQL + Redis 应用

引言&#xff1a;为什么是 Go MySQL Redis&#xff1f;在现代后端技术栈中&#xff0c;Go MySQL Redis 的组合堪称“黄金搭档”&#xff0c;被广泛应用于各种高并发业务场景。Go 语言&#xff1a;以其卓越的并发性能、简洁的语法和高效的执行效率&#xff0c;成为构建高性能…

Excel超级处理器,多个word表格模板中内容提取到Excel表格中

在职场中&#xff0c;很多人习惯在word里插入表格&#xff0c;设计模板&#xff0c;填写内容&#xff0c;一旦有多个word文件需要整理在excel表格中&#xff0c;最常见的工作方式就是每个word文件打开&#xff0c;复制&#xff0c;粘贴到excel表格里&#xff0c;这样的工作方式…

前端工程化:ES6特性

本文为个人学习笔记整理&#xff0c;仅供交流参考&#xff0c;非专业教学资料&#xff0c;内容请自行甄别 文章目录一、let与var1.1、越狱问题1.2、变量的重复声明1.3、变量提升问题二、解构2.1、数组解构2.2、对象解构2.3、方法解构三、链判断四、参数默认值五、箭头函数六、模…

大屏项目展示

一、项目克隆与基础操作 我们参考的项目 互联网设备可视化平台---IofTV-Screen: 🔥一个基于 vue、datav、Echart 框架的物联网可视化(大屏展示)模板,提供数据动态刷新渲染、屏幕适应、数据滚动配置,内部图表自由替换、Mixins注入等功能,持续更新.... 将次项目克隆到本…

基于R语言地理加权回归、主成份分析、判别分析等空间异质性数据分析实践技术应用

在自然和社会科学领域有大量与地理或空间有关的数据&#xff0c;这一类数据一般具有严重的空间异质性&#xff0c;而通常的统计学方法并不能处理空间异质性&#xff0c;因而对此类型的数据无能为力。以地理加权回归为基础的一系列方法&#xff1a;经典地理加权回归&#xff0c;…

【Flask 基础 ①】 | 路由、参数与模板渲染

0 序言 Flask 是 Python 生态中一款轻量级 Web 框架&#xff0c;以简洁、灵活著称。 学习 Flask 的意义在于&#xff1a; 快速开发&#xff1a;通过少量代码即可搭建功能完整的 Web 应用&#xff1b;理解原理&#xff1a;其设计清晰体现了 Web 框架的核心逻辑&#xff0c;如路由…

wordpress登陆前登陆后显示不同的顶部菜单

在WordPress中让“未登录”和“已登录”用户看到不同的顶部菜单&#xff0c;最干净、最安全、最可维护的做法是&#xff1a; 在同一个菜单位置(themelocation)里&#xff0c;根据is_user_logged_in()动态切换菜单。 下面给出三种常见实现方式&#xff0c;按推荐程度排序。任选…

【昇腾推理PaddleOCR】生产级部署方式

已知的在昇腾上推理Paddle OCR有三种方法&#xff1a; 概要&#xff1a; PyTorch官方提供了昇腾插件包&#xff0c;安装后虽然可以支持PytorchOCR和PaddlePaddle的推理任务&#xff0c;但性能较低。换句话说&#xff0c;PaddlePaddle框架层面支持了昇腾&#xff0c;但具体到某个…

LangChain摘要记忆组件的使用与解析

01. 摘要记忆组件的类型 在 LangChain 中使用缓冲记忆组件要不就保存所有信息&#xff08;占用过多容量&#xff09;&#xff0c;要不就保留最近的记忆信息&#xff08;丢失太多重要信息&#xff09;&#xff0c;那么有没有一种情况是既要又要呢&#xff1f; 所以折中方案就出…

NAT与智能选路

1、NAT 基础概念核心作用&#xff1a;私网地址无法在 Internet 上直接使用和分配&#xff0c;NAT 通过将私有地址与公有地址及端口进行转换&#xff0c;实现私网与公网的通信。转换示例&#xff1a;内网用户&#xff08;10.1.1.1&#xff09;访问外网 FTP Server&#xff08;12…

【05】VisionMaster入门到精通——圆查找

文章目录1 运行参数先检测出多个边缘点然后拟合成圆形&#xff0c;可用于圆的定位与测量 1 运行参数 先检测出多个边缘点然后拟合成圆形&#xff0c;可用于圆的定位与测量——运行参数 扇环半径——圆环ROI的内外圆半经&#xff1b; 边绿类型 最强——只检测扫描范围内梯度最…