动态规划——矩阵中的最短路径长度
文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。
题目
假设我们有一个 n 乘以 n 的矩阵 w[n][n]。矩阵存储的都是正整数。棋子起始位置在左上角,终止位置在右下角。我们将棋子从左上角移动到右下角。每次只能向右或者向下移动一位。从左上角到右下角,会有很多不同的路径可以走。我们把每条路径经过的数字加起来看作路径的长度。那从左上角移动到右下角的最短路径长度是多少呢?
回溯法
从 (0, 0) 走到 (n-1, n-1),每一步都有向下或者向右2种选择方式。当走到 (n-1, n-1)停止。
写回溯代码中,递归函数f的参数,仅仅与状态有关,与状态无关的变量(例如n、m、grid)都作为类的实例变量保存起来。
public class MatrixShortestPath {private int n;private int m;private int min;private int[][] grid;public int minPathSum(int[][] grid) {if(grid==null || grid.length==0) return 0;n = grid.length;m = grid[0].length;this.grid = grid;min = Integer.MAX_VALUE;f(0,0,0);return min;}private void f(int i,int j,int currentSum){//System.out.println(i+" " +j);if(i==n-1 && j>=m || i>=n && j==m-1){min = Math.min(min,currentSum);return;}if(i>=n || j>=m) return;f(i,j+1,currentSum+grid[i][j]);f(i+1,j,currentSum+grid[i][j]);}
}
我们根据上面这个特殊的例子,把回溯求解问题的递归树画出来。
递归树的每一个节点表示一个状态,用(i,j,currentPathSum),表示当前将要处理第i行第j列的数据,在处理之前已经走过的路径长度是currentPathSum。
在递归树上能看到虽然(i,j,currentPathSum)重复的不存在,但是这道题目要求的是到达(n-1,n-1)的时候最短路径长度,所以在处理的时候只需要知道到达(i,j)的时候最短的路径长度是多少,其余节点就无需向下扩散了。例如f(1,2,9),f(1,2,5),f(1,2,3)。那只需要保留f(1,2,3),f(1,2,9),f(1,2,5)无需向下扩散。因为达到(1,2)节点之后,仍然是向右、向下走,与currentPathSum等于5、9,3都无关。这样就保证了节点不会指数级增长。
对递归树剪枝——加缓存
int[][] memo,memo[i][j]=currentSum。当调用f(1,2,3)的时候,如果memo[1][2]值为0 ,那就设置memo[1][2]=3,向下计算。当遇到f(1,2,9)的时候,发现memo[1][2]=3,而9>3,则不再计算。
代码的变化就是加了判断:memo[i][j]==0∣∣memo[i][j]>currentSummemo[i][j]==0 || memo[i][j]>currentSummemo[i][j]==0∣∣memo[i][j]>currentSum。
public class MatrixShortestPath {private int n;private int m;private int min;private int[][] grid;private int[][] memo;public int minPathSum(int[][] grid) {if(grid==null || grid.length==0) return 0;n = grid.length;m = grid[0].length;this.grid = grid;min = Integer.MAX_VALUE;memo = new int[n][m];f(0,0,0);return min;}private void f(int i,int j,int currentSum){//System.out.println(i+" " +j+" "+currentSum);if(i==n-1 && j>=m || i>=n && j==m-1){min = Math.min(min,currentSum);return;}if(i>=n || j>=m) return;if(memo[i][j]==0 || memo[i][j]>currentSum){memo[i][j] = currentSum;f(i,j+1,currentSum+grid[i][j]);f(i+1,j,currentSum+grid[i][j]);}}
}
对递归树剪枝——动态规划
状态转移表
接下来我们按照这种方式,计算状态转移表。我们画出一个二维状态表,表中的行、列表示棋子所在的位置,表中的数值表示从起点到这个位置的最短路径。注意:这里状态表的值的含义与递归树中的值的含义发生了变化。我们看到在递归树中到达最后一个位置,还需要currentPathSum+matrix[i][j]。但在表中是不需要的。
如果把表定义为int[][] dp ,dp[i][j]=到达(i,j)位置的最短路径。我们想要的返回值就是dp[n-1][m-1]。
我们按照决策过程,通过不断状态递推演进,将状态表填好。为了方便代码实现,我们按行来进行依次填充。
初始化这一步是很重要的。
对dp[0][0]=grid[0][0]。
对于第0行,大于0的列,只能从左侧的位置转移到当前位置:dp[0][j-1]+grid[0][j]。
对于第0列,只能从上面的位置转移到当前位置:dp[i][0]=dp[i-1][0]+grid[i][0]。
代码:
public int minDistDP(int[][] matrix ,int n){int[][] states = new int[n][n];//第一行for(int j=0;j<n;j++){if(j==0){states[0][j] = matrix[0][j];}else{states[0][j] = states[0][j-1]+matrix[0][j];}}//第一列for(int i=1;i<n;i++){states[i][0] = states[i-1][0]+matrix[i][0];}for(int i=1;i<n;i++){for(int j=1;j<n;j++){states[i][j] = Math.min(states[i-1][j],states[i][j-1])+matrix[i][j];}}return states[n-1][n-1];}
状态转移方程
定义int[][] dp ,dp[i][j]=到达(i,j)位置的最短路径。
我们知道可以从(i-1,j)或者(i,j-1)两个状态到达(i,j)。
从 (i-1,j)到达(i,j),路径和需要增加grid[i][j],也就是说dp[i][j]=dp[i-1][j]+grid[i][j]。
从(i,j-1)到达(i,j),路径和需要增加grid[i][j],也就是说dp[i][j]=dp[i][j-1]+grid[i][j]。
那么转移方程就是
dp[i][j] = min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j])
=min(dp[i-1][j],dp[i][j-1])+grid[i][j]
初始化:
对dp[0][0]=grid[0][0]。
对于第0行,大于0的列,只能从左侧的位置转移到当前位置:dp[0][j-1]+grid[0][j]。
对于第0列,只能从上面的位置转移到当前位置:dp[i][0]=dp[i-1][0]+grid[i][0]。
实现代码:
public int minPathSum(int[][] grid) {if(grid==null || grid.length==0) return 0;int n = grid.length;int m = grid[0].length; int[][] dp = new int[n][m];for(int j=0;j<m;j++){dp[0][j] = (j==0?grid[0][0]:dp[0][j-1]+grid[0][j]);}for(int i=1;i<n;i++){dp[i][0] = dp[i-1][0]+grid[i][0];}for(int i=1;i<n;i++){for(int j=1;j<m;j++){dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];}}return dp[n-1][m-1];}
类似题目
可以用相同的思路处理 LeetCode 322 零钱兑换。
动态规划——矩阵中的最短路径长度相关推荐
- 动态规划-矩阵中的最短路径
一.牛客 NC59 矩阵的最小路径和 给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和. 数 ...
- 数据结构——图-最短路径长度中最大的一个
#include<stdio.h> #include<string.h> #define INF 32767 #define MAXVEX 30 int dist[MAXVEX ...
- 【组合数学+动态规划】在如下8*6的矩阵中,请计算从A移动到B一共有____种走法。要求每次只能向上或向右移动一格,并且不能经过P。...
在如下8*6的矩阵中,请计算从A移动到B一共有__种走法.要求每次只能向上或向右移动一格,并且不能经过P. A:456 B:492 C:568 D:626 E:680 F:702 解析: ...
- 矩阵中的最长递增路径
矩阵中的最长递增路径 文章目录 矩阵中的最长递增路径 一.问题描述 **方法一:朴素的深度优先搜索 [超时]** **方法二:记忆化深度优先搜索 [通过]** **方法三:动态规划** **方法四:广 ...
- 【vivo2021届秋季校招编程题】【java】广度优先搜索(BFS)/深度优先搜索(DFS)找最短路径长度
vivo2021届秋季校招编程题 图中 找两点间的最短路径长度 广度搜索bfs/深度搜索dfs vivo游戏中心的运营小伙伴最近接到一款新游戏的上架申请,为了保障用户体验,运营同学将按运营流程和规范对 ...
- 01二维矩阵中最大全为1的正方形maxSquare——经典DP问题(二维)
在一个二维01矩阵中找到全为1的最大正方形 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 以矩阵中每一个点作为正方形右下角点来处理,而以该点为右下角点的最大边长最多比 ...
- 77. Leetcode 1439. 有序矩阵中的第 k 个最小数组和 (堆-技巧二-多路归并)
技巧二 - 多路归并其实这个技巧,叫做多指针优化可能会更合适,只不过这个名字实在太过朴素且容易和双指 针什么的混淆,因此我给 ta 起了个别致的名字 - 多路归并.多路体现在:有多条候选路线.代码上, ...
- 算法练习day20——190411(重建二叉树、斐波那契数列、跳台阶、矩形覆盖、变态跳台阶、旋转数组的最小数字、矩阵中的路径)
1.重建二叉树 根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字. preorder = [3,9,20,15,7].inorder = [9 ...
- 【算法】广度遍历算法的应用 求出距离顶点v0的最短路径长度为最长的一个顶点,图结构的bfs生成树及其双亲表示形式
例: 求出距离顶点v0的最短路径长度为最长的一个顶点,并要求尽可能节省时间 分析: 用bfs算法(利用bfs算法的层次特性): 从v0出发进行广度遍历时, 最后一层的顶点距离v0的最短路径长度最长.因 ...
最新文章
- SpringMVC - 1.DispatcherServlet
- 洛谷 P2573 [SCOI2012]滑雪
- 2009年5月软件设计师考前预测试题及考点解析
- 图像形状特征提取c语言,OpenCV_局部图像特征的提取与匹配_源代码
- 再谈初学者关心的ssh应用方方面面
- Ubuntu 学习系列-安装Flash播放器
- java 指定格式的date_指定格式的日期字符串转化成java.util.Date类型日期对象
- 网络蚂蚁(netants) v1.25 国际版 bt
- python爬虫当当网图书信息_利用python爬虫可视化分析当当网的图书数据!
- 国外调查问卷怎么做?
- html5的video播放腾讯视频播放器,html 5中使用video元素制作一个影片播放器
- tnl 的 masterServer, client server 架构学习笔记
- 【观察】并购魔方安全,联软科技再启新征程
- 写给女儿高中编程课老师的一封信
- 巴比特 | 元宇宙每日必读:继续加码!韩国政府将投资179亿韩元扶持本国元宇宙企业的内容开发及海外扩张...
- android 微信授权获取用户个人信息
- 这些隐藏功能你知道吗
- BeautifulSoup 与 Xpath
- 输入相应的数打印三角形
- P1462 通往奥格瑞玛的道路