【动态规划】路径问题模型

news2025/6/5 18:35:18

【动态规划】路径问题模型

文章目录

  • 【动态规划】路径问题模型
    • 前言
    • 一、不同路径
    • 二、不同路径-2
    • 三、珠宝的最高价值
    • 四、下降路径最小和
    • 五、最小路径和
    • 六、地下城游戏
    • 总结

前言

​ 本文将从基础的不同路径问题开始,逐步深入到更复杂的最小路径和等问题,最终探讨DP在其他方面的应用。每个例题都将详细介绍状态表示、状态转移方程、初始化过程、填表顺序和返回值,以确保读者能够清晰地理解DP的核心原理。


一、不同路径

题目链接:62. 不同路径 - 力扣(LeetCode)

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

算法流程:

  1. 状态表示

由于题中所问要从左上角到右下角一共有多少种不同路径,所以二维dp数组的第 i 行第 j 列就直接认定为从左上角到达了该位置一共多少种方式。

dp[i] [j] 表示:走到 [i, j] 位置,一共有多少种方式

  1. 状态转移方程

由于题目中给定每一步都只能往下走或往右走,所以到达 [i, j] 位置的前一步所在的位置也只能是 [i - 1, j] 和 [i, j - 1],那么我们就可以得到状态转移方程:

dp[i] [j] = dp [i - 1] [j] + dp [i] [j - 1]

  1. 初始化

当我们考虑从左上角开始对dp数组进行赋值时,由于 [i, j] 位置的值需要用到 [i - 1, j] 和 [i, j - 1] 位置,所以在对第一行和第一列直接使用循环赋值会造成访问越界。于是,给原dp数组上面加一行、左面加一列,这样就能避开对首行首列赋值需要考虑的特殊情况。

在这里插入图片描述

由于我们需要先给出起始位置的值和首行首列的值,所以将首行首列所有值都初始化为0,接着将dp[0] [1]或dp[1] [0] 位置的值设为 1 即可。

  1. 填表顺序

从左往右、从上往下

  1. 返回值

根据新dp数组,返回原数组右下角位置值变为 dp[m] [n] 的值。

示例代码:

int uniquePaths(int m, int n) 
{
    // 1.构建dp表 dp[i][j]表示到该位置一共多少种不同路径
    vector<vector<int>> vv(m + 1, vector<int>(n + 1, 0));

    // 2.初始化 为递推赋值准备
    vv[0][1] = 1;

    // 3.填表
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            vv[i][j] = vv[i - 1][j] + vv[i][j - 1];
        }
    }

    // 4.返回结果
    return vv[m][n];
}

二、不同路径-2

题目链接:63. 不同路径 II - 力扣(LeetCode)

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

算法流程:

  1. 状态表示

对于此题要求到达右下角一共有多少种路径,我们不妨采用与地图对应的 dp 数组,那么数组中元素就有了相应的意义

dp[i] [j] 表示:走到 [i, j] 位置处,一共有多少种方式

  1. 状态转移方程

由于题目中给定每一步都只能往下走或往右走,所以到达 [i, j] 位置的前一步所在的位置也只能是 [i - 1, j] 和 [i, j - 1],但是 [i - 1, j] 和 [i, j - 1] 位置都可能是有障碍物的,从上往下或者从左往右相应都有可能是到不了 [i, j] 位置的,所以此时 [i, j] 的方法数应当为 0,从实际代码中表示时就需要体现判断是否有障碍物这个逻辑,接着才能进行叠加。

  1. 初始化

同样的在首行和首列添加辅助行列,相应的 i 位置变为 i + 1, j 同理。将新添加的首行和首列直接初始化为 0 ,接着将dp[0] [1]或dp[1] [0] 位置的值设为 1 即可。

  1. 填表顺序

从左往右、从上往下

  1. 返回值

根据新dp数组,返回原数组右下角位置值变为 dp[m] [n] 的值。

示例代码:

int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
    size_t m = obstacleGrid.size();
    size_t n = obstacleGrid.at(0).size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));

    // 初始化
    dp[0][1] = 1;
    for (int i = 1; i < m + 1; i++)
    {
        for (int j = 1; j < n + 1; j++)
        {
            if (obstacleGrid[i - 1][j - 1] != 1)
            {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
            else
            {
                dp[i][j] = 0;
            }
        }
    }
    return dp[m][n];
}

三、珠宝的最高价值

题目链接:LCR 166. 珠宝的最高价值 - 力扣

现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为:

  • 只能从架子的左上角开始拿珠宝
  • 每次可以移动到右侧或下侧的相邻位置
  • 到达珠宝架子的右下角时,停止拿取

注意:珠宝的价值都是大于 0 的。除非这个架子上没有任何珠宝,比如 frame = [[0]]

算法流程:

  1. 状态表示

dp[i] [j]表示:走到 [i, j] 位置处,此时的最大价值。

  1. 状态转移方程

对于dp[i] [j] 而言,能到达 [i, j] 位置的只有 [i - 1] [j] 和 [i] [j - 1] 两个位置,由于需要到达 [i, j] 位置只能存在一条最大价值路径(金额相同算一条),所以

  • 从 [i - 1, j] 到 [i, j] 位置的价值:dp[i - 1] [j] + grid[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]

  1. 初始化

由于每个当前位置都与上面、左面位置的值有关系,所以在上面和左面各自新加辅助行和辅助列用于帮助我们赋值。将新添加的首行和首列直接初始化为 0 即可

  1. 填表顺序

从左往右、从上往下

  1. 返回值

根据新dp数组,返回原数组右下角位置值变为 dp[m] [n] 的值。

示例代码:

int jewelleryValue(vector<vector<int>>& frame) {
    size_t m = frame.size();
    size_t n = frame.at(0).size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));

    // 初始化
    dp[1][1] = frame[0][0];

    // 填表
    for (size_t i = 1; i < m + 1; i++)
    {
        for (size_t j = 1; j < n + 1; j++)
        {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + frame[i - 1][j - 1];
        }
    }

    return dp[m][n];
}

四、下降路径最小和

题目链接:931. 下降路径最小和 - 力扣(LeetCode)

给你一个 n x n方形 整数数组 matrix ,请你找出并返回通过 matrix下降路径最小和

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)(row + 1, col) 或者 (row + 1, col + 1)

算法流程:

  1. 状态表示

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

  1. 状态转移方程

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

  • 从正上方 [i - 1, j] 位置转移到 [i, j] 位置;
  • 从左上方 [i - 1, j - 1] 位置转移到 [i, j] 位置;
  • 从右上方 [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. 初始化

由于每个当前位置都与上面三个位置(正上方、左上方、右上方)的值有关系,所以在上面和两侧各自新加辅助行和辅助列用于帮助我们赋值,即增加一行两列。因为我们既定的dp[i] [j] 表示最小值,那么我们就应当把新加行列中的值都初始化为无穷大,然后将第一行初始化为 0 即可

在这里插入图片描述

  1. 填表顺序

从上往下

  1. 返回值

题目要求只要到达最后一行没有指定具体位置,因此这里应该返回 dp 表中最后一行的最小值

示例代码:

int minFallingPathSum(vector<vector<int>>& matrix) {
    int m = matrix.size();
    int n = matrix.at(0).size();

    vector<vector<int>> dp(m + 1, vector<int>(n + 2, INT_MAX));
    // # # # # # #
    // #         #
    // #         #
    // #         #
    // #         #

    // 初始化
    for (int j = 0; j < n + 2; j++)
    {
        dp[0][j] = 0;
    }

    // 填表
    for (int i = 1; i < m + 1; i++)
    {
        for (int j = 1; j < n + 1; j++)
        {
            dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]),
                dp[i - 1][j + 1]) + matrix[i - 1][j - 1];
        }
    }

    // 取dp表最后一行的最小值
    int min = INT_MAX;
    for (int j = 1; j < n + 1; j++)
    {
        min = dp[m][j] < min ? dp[m][j] : min;
    }
    return min;
}

五、最小路径和

题目链接:64. 最小路径和 - 力扣(LeetCode)

给定一个包含非负整数的 *m* x *n* 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

**说明:**每次只能向下或者向右移动一步。

算法流程:

  1. 状态表示

到达 [i, j] 位置时,所有从左上角到该位置的路径中的最小和。

  1. 状态转移方程

对于普遍位置 [i, j] ,由于前一个位置到当前 [i, j] 位置只有从上向下和从左往右两种可能,所以到达该位置可能有两种情况:

  • 从左边 [i, j - 1] 位置转移到 [i, j] 位置;
  • 从上方 [i - 1, j] 位置转移到 [i, j] 位置;

我们所要的是上面两种情况下的最小值,再加上自身 [i, j] 位置矩阵的对应值,所以状态转移方程:

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

  1. 初始化

在首行和首列添加辅助行列,相应的 i 位置变为 i + 1, j 同理。由于求最小和,为了避免辅助行列中的值对后续填表赋值产生负面影响,将新添加的首行和首列直接初始化为 INT_MAX,接着将 dp[0] [1] 和 dp[1] [0] 位置的值都设为 0 即可。

  1. 填表顺序

从左上角到右下角

  1. 返回值

返回新 dp 表中最后一个元素的值,即 dp[m] [n],它代表了从左上角到右下角的最小路径和。

示例代码:

int minPathSum(vector<vector<int>>& grid) {
    int m = grid.size();
    int n = grid.at(0).size();

    // 初始化
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));     // dp数组内的值由min()决定,不能受辅助方格的影响,所以辅助方格初始化为极大值
    dp[1][0] = dp[0][1] = 0;    // 为了正确初始化dp[1][1]的值

    for (int i = 1; i < m + 1; i++)
    {
        for (int j = 1; j < n + 1; j++)
        {
            dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
        }
    }

    return dp[m][n];
}

六、地下城游戏

题目链接:地下城游戏

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

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

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

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

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

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

算法流程:

  1. 状态表示

到达 [i, j] 位置时,骑士为了能够继续前进至少需要的健康点数

  1. 状态转移方程

对于普遍位置 [i, j],骑士到达该位置可能有两种情况:

  • 从左边 [i, j - 1] 位置转移到 [i, j] 位置;
  • 从上方 [i - 1, j] 位置转移到 [i, j] 位置;

我们所要的是上面两种情况下所需健康点数的较小值,再减去当前房间的将要扣除的血量值(如果是负数则设为1),所以状态转移方程:

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

  1. 初始化

由于骑士每次只向右或者向下行进且其自身健康点数不能小于等于0,所以我们将辅助行和辅助列初始化为无穷大,以确保骑士在任何情况下都不会因为这些辅助值而死亡。同时,将右下角的初始值设为1,表示骑士至少需要1点健康点数才能拯救公主。

在这里插入图片描述

  1. 填表顺序

从下往上,从右往左

  1. 返回值

由于骑士是从左上角出发,所以我们需要返回 dp[0] [0],即骑士在出发点至少需要的健康点数

示例代码:

int calculateMinimumHP(vector<vector<int>>& dungeon) {
    int m = dungeon.size();
    int n = dungeon.at(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];
            if (dp[i][j] <= 0) { dp[i][j] = 1; }    // 所需血量为负数,说明血量太充足,至少需要一滴血作为初始生命值
        }
    }

    return dp[0][0];
}

总结

​ 通过本文的学习,我们了解了DP的基本组成部分,包括状态的定义、状态转移的逻辑、初始化的重要性、填表顺序的确定以及最终结果的获取。我们还探讨了DP在不同路径、最小/最优路径选择以及其他相关问题中的应用。

在这里插入图片描述

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

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

相关文章

webpack or vite? vuex or pinia?

2022.2.18, 新建一个vue3的项目&#xff0c;过程如下&#xff1a; 目录结构如下&#xff1a; 当还在犹豫选择webpack还是vite&#xff0c;vuex或者pinia的时候&#xff0c;尤大大已经给出了默认选择&#xff0c;vite && pinia。

Spring Boot 学习(5)——开发流程:快速入门

花了几天的时间&#xff0c;整出个 “hello spring boot”&#xff0c;并且把它从 2 搞到了 3。 纸上得来终觉浅&#xff01;自己实践出真知&#xff01;现在再回头来囫囵一遍&#xff0c;加深下印象。回想下从前自觉某一编程语言大都如此&#xff0c;先找到简单示例照着画一遍…

【LAMMPS学习】八、基础知识(2.7)NEMD 模拟

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

深入微服务框架:构建高效、可扩展与弹性的现代应用架构

前言&#xff1a;当今快速迭代和多变的商业环境中&#xff0c;传统的单体应用程序面临着一系列挑战&#xff0c;包括难以管理复杂性、缺乏灵活性以及无法有效扩展等问题。随着业务需求的不断增长和技术栈的不断演进&#xff0c;企业亟需一种更加模块化、易于管理和扩展的应用程…

【Godot4.2】CanvasItem绘图函数全解析 - 8.绘制点索引

概述 在示意图绘制过程中或者测试过程中&#xff0c;可能需要标记点的索引。 最常见的形式就是用一个圆圈作为背景&#xff0c;用阿拉伯数字作为索引。 实现的重点是动态计算背景圆的半径。原理是&#xff0c;获取字符串的矩形&#xff0c;取对角线长度的一半作为外接圆的半…

Servlet测试1

通过按钮提交get,post请求&#xff0c;并且后端响应数据&#xff0c;显示到前端 当点击get按钮时 是发起Get请求 后端接收到Get请求后&#xff0c;把数据写入到body内 当点击pst按钮时 是发起Post请求 后端接收到Post请求后&#xff0c;把数据写入到body内 之后前端就从bod…

【避坑/个人总结】CARLA仿真遇到问题总结1

问题描述 执行以下命令时&#xff1a; ./CarlaUE4.sh // 以及 ros2 launch carla_shenlan_bridge_ego_vis carla_bridge_ego_vehilce.launch.py 出现以下的问题&#xff1a; 解决方法&#xff1a; 更新numpy库到1.23的版本

Linux【实战篇】—— NFS服务搭建与配置

目录 一、介绍 1.1什么是NFS&#xff1f; 1.2客户端与服务端之间的NFS如何进行数据传输&#xff1f; 1.3RPC和NFS的启动顺序 1.4NFS服务 系统守护进程 二、安装NFS服务端 2.1安装NFS服务 2.2 创建共享目录 2.3创建共享目录首页文件 2.4关闭防火墙 2.5启动NFS服务 2.…

【蓝桥杯】2024年第15届真题题目

试题 A: 握手问题 本题总分&#xff1a; 5 分 【问题描述】 小蓝组织了一场算法交流会议&#xff0c;总共有 50 人参加了本次会议。在会议上&#xff0c; 大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进 行一次握手&#xff08;且仅有一次&a…

【数据恢复软件】:Magnet AXIOM V8.0

Magnet AXIOM V8.0重大更新 1、全新的UI设计 2、更快的相应速度 3、补全工件分析 4、支持亚马逊AWS云数据&#xff08; 获取同一帐户或安全帐户上下文中的快照。 支持Windows实例、加密卷和超过1 TB的卷、具有多个卷的实例等等&#xff01; &#xff09; 5、Bug修复 6、AI支持…

【QT+QGIS跨平台编译】099:【QGIS_CORE跨平台编译】—【错误处理:qgscoordinatereferencesystem.cpp编译不通过】

点击查看专栏目录 文章目录 一、错误信息二、原因分析三、错误处理3.1 qgscoordinatereferencesystem_legacy.h3.2 qgscoordinatereferencesystem.cpp一、错误信息 macOS操作系统中,Release环境下编译qgscoordinatereferencesystem.cpp,出现错误,详细信息如下: 二

【软件设计师】计算机软考下午题试题六,Java设计模式之简单工厂模式。

【软件设计师】计算机软考下午题试题六&#xff0c;Java设计模式之简单工厂模式。 代码如下&#xff1a; //简单工厂模式 public class SimpleFactory {public static void main(String[] args) {Product ProductAFactory.createProduct("A");ProductA.info();Produc…

LeetCode 2924.找到冠军 II:脑筋急转弯——只关心入度

【LetMeFly】2924.找到冠军 II&#xff1a;脑筋急转弯——只关心入度 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-champion-ii/ 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。每支队伍也是 有向无环图&#xff08;DAG&#xff09; 上的一个节…

Docker Image (镜像) 常见命令

Docker Image (镜像) 常用命令 docker images 功能&#xff1a;列出本地所有的镜像。如果镜像 ID 相同&#xff0c;但是 Tag 标签不同&#xff0c;也会被当作不同的条目被列出来。 语法&#xff1a; docker images [options] [REPOSITORY[:TAG]] 别名&#xff1a; docker ima…

使用Pandas实现股票交易数据可视化

一、折线图&#xff1a;展现股价走势 1.1、简单版-股价走势图 # 简洁版import pandas as pdimport matplotlib.pyplot as plt# 读取CSV文件df pd.read_csv(../数据集/格力电器.csv)data df[[high, close]].plot()plt.show() 首先通过df[[high,close]]从df中获取最高价和收盘…

本地PC安装eNSP Pro完成简单的WLAN实验

前言 上个月底华为更新一版eNSP Pro&#xff0c;新增了AC、AP、STA等设备&#xff0c;也就是说可以在eNSP中进行WLAN相关的实验了。之前写过一篇文章《将eNSP Pro部署在华为云是什么体验》介绍了怎么在华为云上部署eNSP Pro&#xff0c;这次使用本地PC机在虚拟机中安装eNSP Pr…

配置交换机端口安全

1、实验目的 通过本实验可以掌握&#xff1a; 交换机管理地址配置及接口配置。查看交换机的MAC地址表。配置静态端口安全、动态端口安全和粘滞端口安全的方法。 2、实验拓扑 配置交换机端口安全的实验拓扑如图所示。 配置交换机端口安全的实验拓扑 3、实验步骤 &#xff…

excel中能不能用substitute函数把文本中某个字符起始的数据全部替换?

希望将A2单元格中&#xff0c;以光交开始的字符全部替换&#xff0c;本以为可以用公式SUBSTITUTE(A2,"光交","")&#xff0c;好像*不起作用。 很可惜的是&#xff0c;不仅SUBSTITUTE函数不支持通配符&#xff0c;像LEFT、RIGHT、MID、REPLACE函数都不支持通…

2024年大唐杯官网模拟题

单选(出题角度很奇怪&#xff0c;不用太纠结&#xff09; 5G NR系统中&#xff0c;基于SSB的NR同频测量在measconfig里最多可以配置&#xff08; &#xff09;个SMTC窗口。 A、3 B、4 C、1 D、2 答案&#xff1a;D 2个 5G 中从BBU到AAU需要保证&#xff08; &#xff09;Gbps…

DHCP服务器的高可靠、高可用+负载均衡配置

一、适用场景 1、DHCP地址池集中化的管理环境中&#xff08;本例建立了200个C类网24位的地址池&#xff09;&#xff1b; 2、全网仅1台合法的DHCP服务器&#xff08;要是它宕机全部断网&#xff0c;本例旨在提高服务器的可靠性、可用性&#xff0c;双DHCP服务器性能上负载均衡…