动态规划——矩阵中的最短路径长度

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

题目

假设我们有一个 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]==0memo[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 零钱兑换。

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

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

相关文章

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第19篇]Shamir密钥交换场景

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 Shamir密钥交换场景是一个被Adi Shamir提出的算法.算法允许多方分割一个密码,例如一个密钥.当足够多的秘密结…

第五十五期:MongoDB数据库误删后的恢复

如果部署的是 MongoDB 复制集&#xff0c;这时还有一线希望&#xff0c;可以通过 oplog 来尽可能的恢复数据;MongoDB 复制集的每一条修改操作都会记录一条 oplog&#xff1b;如果对 MongoDB 做了全量备份 增量备份&#xff0c;那么可以通过备份集及来恢复数据。 作者&#xf…

037-PHP如何返回闭包函数实例

<?php /*: 如何返回闭包函数实例*/# 直接调用将不会输出$txt的内容function demo(){$txt 我爱PHP;$func function () use ($txt) {echo $txt;};# 这里不再直接调用&#xff0c;而且是把实例返回return $func; # 区别于直接写 $func;}# 测试一下 $res demo(); // 函数返…

动态规划——莱文斯坦距离

文章出处&#xff1a;极客时间《数据结构和算法之美》-作者&#xff1a;王争。该系列文章是本人的学习笔记。 莱文斯坦距离 在搜索引擎中会有搜索词纠错的功能。这个功能背后的原理是编辑距离。 编辑距离 编辑距离是量化两个词之间的相似度。 编辑距离是指将一个字符串变为…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第18篇]画一个/描述ECB,CBC,CTR模式的操作

操作模式:块密码的安全性依赖于加解密一个固定长度的明文块.当加密或者解密消息的时候,块是被需要的.我们使用一种操作模式将明文的多个块链接在一起.我们会知道,这种链接在一起的方法是十分重要. 电子密码本(ECB)模式:加密,解密. ECB模式是最直接的方法.明文被分割成m块.每一…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第20篇]Merkle-Damgaard hash函数如何构造

这里讲的是MD变换,MD变换的全称为Merkle-Damgaard变换.我们平时接触的hash函数都是先构造出一个防碰撞的压缩函数.然后先证明这个小的,固定长度的压缩函数是安全的,然后再用它构造一个任意长度的哈希算法.虽然存在很多其它的构造方法,MD是迄今为止最常用的(至少是被用到最多的)…

第五十六期:IPv6只是增加了地址数量?其实真相并没有那么简单!

究竟什么是IPv6?它到底是干啥用的?IPv6的全称是Internet Protocol version 6。其中&#xff0c;Internet Protocol译为“互联网协议”。所以&#xff0c;IPv6就是互联网协议第6版。 作者&#xff1a;小枣君 10月20日&#xff0c;在乌镇举办的第六届世界互联网大会上&#x…

spring学习(10):创建项目(自动装配)

首先创建项目 pom.xml的配置文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mav…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第21篇]CRT算法如何提高RSA的性能?

CRT加速RSA&#xff1a;https://www.di-mgt.com.au/crt_rsa.html 转载链接&#xff1a;https://www.cnblogs.com/zhuowangy2k/p/12245513.html

动态规划——最长递增子序列

题目 我们有一个数字序列包含 n 个不同的数字&#xff0c;如何求出这个序列中的最长递增子序列长度&#xff1f;比如 2, 9, 3, 6, 5, 1, 7 这样一组数字序列&#xff0c;它的最长递增子序列就是 2, 3, 5, 7&#xff0c;所以最长递增子序列的长度是 4。 回溯法 数组长度为n&a…

spring学习(11):使用配置类

CompactDisc类 package soundSystem;import org.springframework.stereotype.Component;Component public class CompactDisc {public CompactDisc() {super();System.out.println("compactdisc无参构造方法");}public void play(){System.out.println("正在播…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第22篇]如何用蒙哥马利算法表示一个数字和多个相乘的数字

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 安全和效率 密码学的目标是设计高度安全的密码学协议,但是同时这些协议也应该被有效率的实现.这样就可以一…

动态规划——双11既可以薅羊毛还能花钱最少

淘宝的“双十一”购物节有各种促销活动&#xff0c;比如“满 200 元减 50 元”。假设你的购物车中有 n 个&#xff08;n>100&#xff09;想买的商品&#xff0c;希望从里面选几个&#xff0c;在凑够满减条件的前提下&#xff0c;让选出来的商品价格总和最大程度地接近满减条…

spring学习(12):使用junit4进行单元测试

pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第23篇]写一个实现蒙哥马利算法的C程序

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 这次博客我将通过对蒙哥马利算法的一个实际的实现&#xff0c;来补充我们上周蒙哥马利算法的理论方面。这个…

spring学习(13):使用junit4进行单元测试续

加入spring test.jar包 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven…

《java程序员修炼之道》pdf书籍

链接&#xff1a;https://pan.baidu.com/s/1bbsTPCpUNI9klh40-8Be7w 提取码&#xff1a;pc57 转载于:https://www.cnblogs.com/pyweb/p/10995145.html

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第24篇]描述一个二进制m组的滑动窗口指数算法

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 二进制算法 二进制模幂算法和传统的求幂的二次方方法非常像。实际上&#xff0c;唯一的不同就是我们把N表示…

第五十八期:从0到1 手把手教你建一个区块链

近期的区块链重回热点&#xff0c;如果你想深入了解区块链&#xff0c;那就来看一下本文&#xff0c;手把手教你构建一个自己的区块链。 作者&#xff1a;Captain编译 近期的区块链重回热点&#xff0c;如果你想深入了解区块链&#xff0c;那就来看一下本文&#xff0c;手把手…

动态规划——硬币找零思路

找零的两种问题 硬币找零问题&#xff0c;有两种。一种用贪心解决&#xff0c;一种用动态规划解决。 问题1&#xff1a;假设我们有 v1&#xff0c;v2&#xff0c;……&#xff0c;vn&#xff08;单位是元&#xff09;这些币值的硬币&#xff0c;它们的张数分别是 c1、c2、…, …