C++算法 —— 动态规划(2)路径问题

这篇具有很好参考价值的文章主要介绍了C++算法 —— 动态规划(2)路径问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以让不懂这个算法的人也能边看边理解。

1、动规思路简介

动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅读题时,学习中的沉浸感就体验到了。

状态表示
状态转移方程
初始化
填表顺序
返回值

动规一般会先创建一个数组,名字为dp,这个数组也叫dp表。通过一些操作,把dp表填满,其中一个值就是答案。dp数组的每一个元素都表明一种状态,我们的第一步就是先确定状态。

状态的确定可能通过题目要求来得知,可能通过经验 + 题目要求来得知,可能在分析过程中,发现的重复子问题来确定状态。还有别的方法来确定状态,但都大同小异,明白了动规,这些思路也会随之产生。状态的确定就是打算让dp[i]表示什么,这是最重要的一步。状态表示通常用某个位置为结尾或者起点来确定,这点在下面的题解中慢慢领会。

状态转移方程,就是dp[i]等于什么,状态转移方程就是什么。像斐波那契数列,dp[i] = dp[i - 1] + dp[i - 2]。这是最难的一步。一开始,可能状态表示不正确,但不要紧,大胆制定状态,如果没法推出转移方程,没法得到结果,那这个状态表示就是错误的。所以状态表示和状态转移方程是相辅相成的,可以帮你检查自己的思路。

要确定方程,就从最近的一步来划分问题。

初始化,就是要填表,保证其不越界。像第一段所说,动规就是要填表。比如斐波那契数列,如果要填dp[1],那么我们可能需要dp[0]和dp[-1],这就出现越界了,所以为了防止越界,一开始就固定好前两个值,那么第三个值就是前两个值之和,也不会出现越界。初始化的方式不止这一点,有些问题,假使一个位置是由前面2个位置得到的,我们初始化最一开始两个位置,然后写代码,会发现不够高效,这时候就需要设置一个虚拟节点,一维数组的话就是在数组0位置处左边再填一个位置,整个dp数组的元素个数也+1,让原先的dp[0]变为现在的dp[1],二维数组则是要填一列和一行,设置好这一行一列的所有值,原先数组的第一列第一行就可以通过新填的来初始化,这个初始化方法在下面的题解中慢慢领会。

第二种初始化方法的注意事项就是如何初始化虚拟节点的数值来保证填表的结果是正确的,以及新表和旧表的映射关系的维护,也就是下标的变化。

填表顺序。填当前状态的时候,所需要的状态应当已经计算过了。还是斐波那契数列,填dp[4]的时候,dp[3]和dp[2]应当都已经计算好了,那么dp[4]也就出来了,此时的顺序就是从左到右。还有别的顺序,要依据前面的分析来决定。

返回值,要看题目要求。

2、不同路径

62. 不同路径

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

这是一个很经典的题目了,还有类似的题目是走迷宫,可走的路径上都是0,代表墙的就是1。

每一步都可以是向右或者向下。动规(1)中状态的表示我们分为以i位置为结尾或者起点,但这里是一个二维的空间,所以就变成以[i, j]为结尾或起点,这道题我们把[i, j]当作结尾。

题目中需要求到达右下角时有多少条路径。在分析状态转移方程时,我们采用最近的一步来分析问题。到达一个点只能是左边的节点向右走,或者上面的节点向下走,才能走到这个节点,而左边或者上面的节点也同样是这两种情况走过来的,所以对于这个题,状态的表示就可以理解成dp[i, j]等于到达这个位置的路径数,而状态转移方程要如何确定?如果是从左边节点向右移动过来的,那么这个节点的路径数应当是左边这个节点的路径数,为什么不加1?要注意,走一步和路径是两个概念,左边节点代表着来到这个节点的路径数,这些路径都只需要再往右走一步即可,不需要再新增一个路径,所以只是路径改变了,但是数量不变,所以dp[i][j] = dp[i][j - 1]。上面的节点向下走一步也同理,dp[i][j] = dp[i - 1][j]。所以状态转移方程就是dp[i][j - 1] + dp[i - 1][j] = dp[i][j]。

第一行和第一列的节点可不全是由这两种情况演变过来的,第一列的节点的左边就越界了,所以我们可以先把第一行第一列的所有位置给初始化固定的数值,然后再一步步向右下角计算。另一种方法就是设立虚拟节点,创建数组时多创建一行一列,初始化新表的第一行第一列,那么旧表的第一行第一列就可以通过新表的第一行第一列来计算得到,计算公式就是dp[i][j - 1] + dp[i - 1][j] = dp[i][j]。,然后向右下角逐渐计算。

现在看貌似第二种方法也不是很高效,这只是因为现在这样的题没法看出来,有些题目用第二种方式代码会更简洁。

这道题我们就用第二种方式来写。根据上面的分析,状态表示就是dp[i, j]代表到达这个位置的路径数,状态转移方程是dp[i][j - 1] + dp[i - 1][j] = dp[i][j]。,初始化时多创建一行一列,填表顺序是从上往下填写每一行,每一行从左到右填写,返回值就是右下角那个位置的值。

这里面还剩初始化的问题没有解决,原数组中第一行第一列,它们的每个位置应当都是1,它们只可能是从左边或者从上面没移动过来的,所以只能是1,而新增的第一行第一列要如何设置数值来让它们都是1?其实我们只需要确定新表中第二行第二列的那个数就行,因为除去第一行第一列,其它数字都可以通过它来得到,所以可以这样设置

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

分析完成,写代码

    int uniquePaths(int m, int n){
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        dp[0][1] = 1;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1;j <= n; j++)
            {
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
            }
        }
        return dp[m][n];
    }

3、不同路径Ⅱ

63. 不同路径 II

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

状态表示是到达[i, j]时一共有多少种方法。状态转移方程有所改变。如果[i, j]是障碍物,那么就没法到这个位置,也就是0;如果不是障碍物,那就和上一个题一样,dp[i][j - 1] + dp[i - 1][j] = dp[i][j],但如果上面或者左边是障碍物怎么办?其实没影响,因为如果是障碍物,那个位置就是0,加上0不影响最终结果。其实和上一个题比较,唯一的不同就是某个位置为障碍物就为0。

初始化时设立虚拟节点,增加一行一列,和上一道题一样,保证第二行第二列那个值正确就行。所以和上面的代码一样,dp[0][1] = 1或者dp[1][0] = 1就行。

填表顺序和上面一样,映射关系要处理好,返回值就是右下角那个值。

    int uniquePathsWithObstacles(vector<vector<int>>& ob) {
        int m = ob.size(), n = ob[0].size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        dp[1][0] = 1;
        for(int i = 1; i <= m ; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(ob[i - 1][j - 1] == 0)//体现了映射关系,我们需要都减去1才能找到原表的元素
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                else dp[i][j] = 0;
            }
        }
        return dp[m][n];
    }

4、礼物的最大价值

剑指 Offer 47. 礼物的最大价值

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

这个题和不同路径那个题相似。题目要求计算走到右下角最多能拿到多少,只能走右边或者下边。对于右下角那个位置的数值,它能拿到的最大价值要从上面节点和左面节点中选择,也就是这两个中的较大值,再加上右下角位置的本身数值,就是最大的价值。既然是这样的思路,那么上面和左面的节点就得是到这个节点时最大的价值,所有状态表示就是dp[i][j]是到达这个位置时的最大价值。

根据上面的分析,当前位置的最大价值是上面和左面节点的较大值,再加上当前位置的价值,所以状态转移方程就是dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1]) + 当前位置的价值。

初始化时,我们还是采用多加一行多加一列的方式,这个方法的注意事项就是新增行列的值要保证后面位置的值要正确,以及新表和旧表的映射关系。原表已经在每个位置都放上了对应的数值,如果新增行列的话,应当不影响原先的数值,所以都设置为0,而vector默认初始化为0。

填表顺序就是每一列从上到下,每一行从左到右,返回值则是右下角那个值。

    int maxValue(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }

5、下降路径最小和

931. 下降路径最小和

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

看题目可能比较懵,但好在题目最后给了公式。从公式中看出来,一个值和要比较的三个值之间呈现一个三角形,一个位置可能由上面3个位置而来,虽然题目中给的是一个值和下面三个值的关系,但我们把它反过来也一样,就变成了一个值和上面3个值。

状态表示还是到达[i, j]位置时最小的下降路径。状态转移方程题目已经给了,dp[i][j] = 上面三个位置的最小值再加上当前位置的数值,这三个位置分别是dp[i - 1][j - 1],dp[i - 1][j],dp[i - 1][j + 1],找出这个三个的最小值就行。

初始化是为了防止越界问题,这次的初始化有所不同,不能直接一行,一列,而是要让新加的行列把整个表包围起来,左右边各新增一列,上面新增一行。如何初始化新增的行列?对于最上面一行来说,也就是新增的行,它不能影响原表第一行的元素,所以都为0;而对于新增的列来说,不能为0,因为我们要的是最小值,新增的列的数值不应当影响比较,所以我们把它初始化为正无穷大。初始化时先都设为正无穷,然后再把第一行全设为0就行。新增行列后,原表中的[0, 0]变为新表的[1, 1],所以新表的[i, j]对应原表的[i - 1][j - 1]。

填表顺序是从上到下。返回值不是右下角的值,因为这个题要求到达最后一行,所以要取最后一行的最小值就行。

    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> dp(n + 1, vector<int>(n + 2, INT_MAX));
        for(int j = 0; j < n + 2; j++)
        {
            dp[0][j] = 0;
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] = min(min(dp[i - 1][j], dp[i - 1][j + 1]), dp[i - 1][j - 1]) + matrix[i - 1][j - 1];
            }
        }
        int ret = INT_MAX;
        for(int j = 1; j <= n; j++)
        {
            ret = min(ret, dp[n][j]);
        }
        return ret;
    }

6、最小路径和

64. 最小路径和

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

这道题和礼物的最大价值是一个题,只不过一个求最大,一个求最小。不过需要注意的是,如果是求最大,我们把新增行列初始化为0,然后找特殊位置;而求最小,则是初始化为正无穷,然后找特殊位置。这个题的特殊位置就是像上图中的左上角位置,它的数值是1,即使加上新增行列中的对应的两个位置的较小值也不能被改变数值,所以新表dp[0][1] = dp[1][0] = 1。其余新增位置设为正无穷即可。

    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
        dp[0][1] = dp[1][0] = 0;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }

7、地下城游戏

174. 地下城游戏

C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(2)路径问题,C++算法,动态规划,算法,c++

这个题看起来稍有点不一样。状态表示应该是什么?还是以某个位置为结尾或者为起点。如果是为结尾,意思是从起点出发,到[i, j]位置时,所需的最低初始健康点数是多少。我们需要用到上面和左面位置来更新当前位置的值,但实际上却不止这点,因为即使我们算出来当前位置的值,我们还需要考虑右面或者下面节点的值,如果它们是个-100,那么很有可能直接死亡,那又得重新计算了,可以自己列举几个数字,按照一条路线试着走,会发现需要不断再改变初始值,再走一遍。所以这个题适合以当前节点为起点,从[i, j]位置出发,到达终点,所需的最低初始健康值,每一步都可能是往右或者往下,当前位置的值要选择右边位置和下边位置的较小值,并且还要考虑当前位置的值,因为当前位置还没过去,所以假设x是最低初始值,那么仅看一条路线,x + 原数组[i][j] >= dp[i][j + 1],也就是x >= dp[i][j + 1] - 原数组[i][j],那么往下走就是x >= dp[i + 1][j] - 原数组[i][j],求右边和下边的较小值就可,但这里仍然有坑点,如果当前位置的值是个负数呢?我们走到这里,可能直接死亡了,所以我们要更换一下dp[i][j],让它和1取较大值,把一个负数变成1,因为我们还要走下去,要至少有一点血量才行。如果走到当前位置,dp[i][j]是正数,这没问题,但如果是负数,说明挂了,我们就得改为1,这样就能保证一路上挂不了,也能求出来最低初始值。

初始化时还是多加一列多加一行,但因为比较的值是右边和下边的,所以我们要在旧表的右边和下边添加一列一行,并且这时候就不用考虑映射关系了,因为旧表的元素在新表的下标还是一样。特殊位置是在右下角那个值,走到右下角时,我们要保证血量还剩至少1,那么它的右边和下边就得是1,而其他位置,因为求最小值,所以为了不影响计算,就初始化成正无穷。

填表顺序和之前不一样,应当是每列从下往上,每行从右往左填。返回值是左上角那个值。

    int calculateMinimumHP(vector<vector<int>>& dg) {
        int m = dg.size(), n = dg[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]) - dg[i][j];
                dp[i][j] = max(1, dp[i][j]);
            }
        }
        return dp[0][0];
    }

结束。文章来源地址https://www.toymoban.com/news/detail-737627.html

到了这里,关于C++算法 —— 动态规划(2)路径问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划:路径和子数组问题(C++)

    1.不同路径(中等) 链接:不同路径 题目描述 做题步骤 状态表示 尝试 定义状态表示为到达[m, n]位置的路径数 。 状态转移方程 通过上述分析,可知状态转移方程为: dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 。 初始化 填表顺序 保证填当前状态时,所需状态已经计算过,从起点出发,

    2024年02月10日
    浏览(22)
  • 基础算法之——【动态规划之路径问题】1

    今天更新动态规划路径问题1,后续会继续更新其他有关动态规划的问题!动态规划的路径问题,顾名思义,就是和路径相关的问题。当然,我们是从最简单的找路径开始! 动态规划的使用方法: 1.确定状态并定义状态数组:(i,j)代表什么意思?dp[i][j]又是什么意思? 2.确

    2024年02月07日
    浏览(29)
  • C++算法初级11——01背包问题(动态规划2)

    辰辰采药 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时

    2024年02月02日
    浏览(31)
  • C++ DP算法,动态规划——背包问题(背包九讲)

    有N件物品和一个容量为 V V V 的背包。放入第i件物品耗费的空间是 C i C_i C i ​ ,得到的价值是 W i W_i W i ​ 。 求解将哪些物品装入背包可使价值总和最大。 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 F [ i , v ] F[i, v] F

    2024年02月16日
    浏览(33)
  • 蓝桥杯(路径 动态规划 C++)

     思路: 1、利用动态规划的思想。 2、用f[i]来记录从第一个点到第i个点的最短距离。 3、f[i]赋值分两种情况,第一种:f[i]为0的时候,也就是第一种刚到i点的情况,记录其距离为最小公倍数;第二种:f[i]已经有值了,比较新的点到其距离和之前点到其距离,取小的赋值。

    2024年02月07日
    浏览(22)
  • 动态规划|【路径问题】|931.下降路径最小和

    目录 题目 题目解析 思路 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 代码 931. 下降路径最小和 给你一个  n x n  的  方形  整数数组  matrix  ,请你找出并返回通过  matrix  的 下降路径   的   最小和  。 下降路径  可以从第一行中的任何元素开始,并从每一

    2024年04月13日
    浏览(32)
  • 【动态规划】路径问题

    冻龟算法系列之路径问题 本文为动态规划的第二章:路径问题,重点讲解关于路径有关的问题,上一篇文章是一维的,那么路径问题就是二维的,通过题目可见需要创建二维的dp表,而以下将通过“解题”的方式去学习动归知识! 创建什么样的dp表,其实看题目就可以看出来

    2024年02月09日
    浏览(29)
  • 动态规划-路径问题

    题目描述: 状态表示: 设dp[i][j]表示到达(i,j)位置时的路径数目。 状态转移方程: dp[i][j]=dp[i-1][j]+dp[i][j-1]。这里分析起来很简单,因为这算是很经典的问题了。机器人只能向下或者向右走,所以到达(i,j)就有两种方式,分别是从(i-1,j)向下以及(i,j-1)向右,那么到达(i,j)位置的

    2024年04月17日
    浏览(26)
  • C++--动态规划路径问题

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

    2024年02月15日
    浏览(30)
  • 动态规划之路径问题

    1.题目链接:不同路径 2.题目描述: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径? 3.问题分析: 对于 动态

    2024年02月11日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包