赞
踩
冻龟算法系列之路径问题
本文为动态规划的第二章:路径问题,重点讲解关于路径有关的问题,上一篇文章是一维的,那么路径问题就是二维的,通过题目可见需要创建二维的dp表,而以下将通过“解题”的方式去学习动归知识!
动态规划基础博客:【动态规划】斐波那契数列模型_s:103的博客-CSDN博客
传送门:力扣92
题目:
越难的dp问题,看示例只能起到了解题目的效果,一般推不出啥普遍的规律,所以接下来就是我们的算法原理,通过动归的思想去理解,才会豁然开朗!
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
表示表示的就是起点到坐标为(i, j)的位置的路径数
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这两种情况的路径数之和!
所以得出状态转移方程:dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
对于这道题的状态表示和状态转移方程,有以下坐标(桃心标记)在填表的时候会出现异常:
因为我们的状态转移方程需要访问到左边一格和上面一格的元素,而在这个边界,会越界
总的来看是:左上角到右下角
注意下标对应!
由于我们扩展了矩阵,所以坐标发生了变化,原(0, 0) 变成 现(1, 1)
则我们的返回值为dp(m, n)
class Solution { public int uniquePaths(int m, int n) { //1. 建立dp表 //2. 初始化 //3. 填表 //4. 返回值 int[][] dp = new int[m + 1][n + 1]; dp[0][1] = 1; for(int i = 1; i < m + 1; i++) { for(int j = 1; j < n + 1; j++) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m][n]; } }
时空复杂度都为:O(N2)
传送门:力扣93
题目:
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
表示表示的就是起点到坐标为(i, j)的位置的路径数
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这两种情况的路径数之和!
到这里,仍然跟第一道题一致,但是此题多出的要点,要考虑到状态转移方程!
也就是说,如果(i, j)为障碍物,则最后一步将不构成一条路径,也就是说无论(i, j - 1)还是(i - 1, j)路径数再多,都不能到达(i, j),所以dp值应该为0!
所以得出状态转移方程:dp[i][j] = o[i][j] == 0 ? dp[i - 1][j] + dp[i][j - 1] : 0;
同样的,进行扩张矩阵 => (m + 1) ×(n + 1)大小
下标对应:返回dp[m] [n]
class Solution { public int uniquePathsWithObstacles(int[][] o) { //1. 创建dp表 //2. 初始化 //3. 填表 //4. 返回值 int m = o.length; int n = o[0].length; int[][] dp = new int[m + 1][n + 1]; dp[0][1] = 1; for(int i = 1; i < m + 1; i++) { for(int j = 1; j < n + 1; j++) { dp[i][j] = o[i - 1][j - 1] == 0 ? dp[i][j - 1] + dp[i - 1][j] : 0; } } return dp[m][n]; } }
传送门:力扣剑指offer47
题目:
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
表示表示的就是起点到坐标为(i, j)的位置的路径最大价值
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这两种情况的较大值!
得出状态转移方程:dp[i][j] = max{dp[i][j - 1], dp[i - 1][j]} + grid[i][j]
老样子,扩张矩阵为 (m + 1)×(n + 1)
从左上角到右下角:从上到下每一行,每一行从左到右
根据下标对应:应该返回dp[m] [n]
class Solution { public int maxValue(int[][] grid) { //1. 创建dp表 //2. 初始化 //3. 填表 //4. 返回值 int m = grid.length; int n = grid[0].length; int[][] dp = new int[m + 1][n + 1]; for(int i = 1; i < m + 1; i++) { for(int j = 1; j < n + 1; j++) { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; } } return dp[m][n]; } }
传送门:力扣931
题目:
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
表示表示的就是“起点”到坐标为(i, j)的位置的最小下降路径长
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这三种情况的较小值
得到状态转移方程:dp[i][j] = min{dp[i - 1][j], dp[i - 1][j - 1], dp[i - 1][j + 1]} + matrix[i][j];
同样的,需要进行扩张矩阵,但是这次有边界问题的是这些:
所以要扩张这么一圈:
整体向下
由于扩张过矩阵,所以要注意下标的对应
应该返回最后一行中的“每个终点”,下降路径长最短的那种情况的值!
class Solution { public int minFallingPathSum(int[][] matrix) { //1. 创建dp表 //2. 初始化 //3. 填表 //4. 返回值 int m = matrix.length; int n = matrix.length; int[][] dp = new int[m + 1][n + 2]; for(int i = 1; i < m + 1; i++) { dp[i][0] = Integer.MAX_VALUE; dp[i][n + 1] = Integer.MAX_VALUE; } for(int i = 1; i < m + 1; i++) { for(int j = 1; j < n + 1; j++) { dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i - 1][j - 1]) , dp[i - 1][j + 1]) + matrix[i - 1][j - 1]; } } int min = Integer.MAX_VALUE; for(int i = 1; i < n + 1; i++) { min = Math.min(min, dp[m][i]); } return min; } }
千万注意下标对应!
传送门:力扣64
题目:
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
表示的就是(i, j)的位置**到达终点的路径最小权值和**
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这两种情况的较小值!
所以得出状态转移方程:
dp[i][j] = min{dp[i + 1][j], dp[i][j + 1]} + grid[i][j]
与“以某点为结尾”的方法不同的是,其面临边界的坐标不同:
所以扩张矩阵应该是这样的:
而我们要保证:
右下到左上:从下往上每一行,每一行从右到左
返回dp[0] [0],代表起点到终点的最小路径权值和
class Solution { public int minPathSum(int[][] grid) { //1. 创建dp表 //2. 初始化 //3. 填表 //4. 返回值 int m = grid.length; int n = grid[0].length; int dp[][] = new int[m + 1][n + 1]; for(int i = 0; i < m + 1; i++) { dp[i][n] = Integer.MAX_VALUE; } for(int j = 0; j < n + 1; j++) { dp[m][j] = Integer.MAX_VALUE; } dp[m][n - 1] = 0; dp[m - 1][n] = 0; for(int i = m - 1; i >= 0; i--) { for(int j = n - 1; j >= 0; j--) { dp[i][j] = Math.min(dp[i + 1][j], dp[i][j + 1]) + grid[i][j]; } } return dp[0][0]; } }
本题下标对应没啥问题~
传送门:力扣174
题目:
我们需要通过经验 + 题目要求去决定状态表示:
再根据经验,一般dp表的其中一值就应该是答案!
综合得到状态表示:dp[i][j]
的含义是,以此位置开始,到达公主返回需要的最少初始血量
为什么不能“以某点为结尾”?
通过刚才的分析,明显在“正推”的过程中,时不时就要更新初始血量,及其难以用代码去实现(而这也是不应该出现在动态规划中的现象)
- 我们更希望,一个位置的血量能一次就确定下来
我们可以看出,如果是“以某点为终点”去研究,例如到达(0, 2)和(1, 2)时的dp值也没啥问题,但是这个方法并没有记忆当前回复的血量,所以会导致到达公主房间时得出的dp值偏高
- 而此时,我们是需要其后面节点的值去推导这个dp值,但是这并不能做到!
同样的套路,我们需要根据已确定的dp表的值来推导dp[i] [j]的值,并且牢记dp表的状态表示!
“最近一步”可以理解为“必然事件”
那么dp[i] [j]就是为这两种情况的较小值,当然dp[i] [j]必然 >= 1!
所以得出状态转移方程:
dp[i][j] = max{min{dp[i + 1][j], dp[i][j + 1]} - dungeon[i][j], 1}
同样的,应该扩张成这样:
骑士到达公主房间,如果有扣血,则扣血后应该剩一滴血!
右下角到左上角:从下到上每一行,每一行从右到左
返回dp[0] [0],代表从起点到公主房间的最少初始血量
class Solution { public int calculateMinimumHP(int[][] dungeon) { //1. 创建dp表 //2. 初始化 //3. 填表 //4. 返回值 int m = dungeon.length; int n = dungeon[0].length; int[][] dp = new int[m + 1][n + 1]; for(int i = 0; i < m + 1; i++) { dp[i][n] = Integer.MAX_VALUE; } for(int j = 0; j < n + 1; j++) { dp[m][j] = Integer.MAX_VALUE; } dp[m - 1][n] = 1; for(int i = m - 1; i >= 0; i--) { for(int j = n - 1; j >= 0; j--) { dp[i][j] = Math.max( 1, Math.min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j] ); } } return dp[0][0]; } }
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/484231
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。