一、矩阵的最小路径和

1 3 5 9         1 4 9 18         1  4  9  18
8 1 3 4 9 9 5 8 12
5 0 6 1 14 14 5 11 12
8 8 4 0 22 22 13 15 12
最小路径和最小的路径为:1 → 3 → 1 → 0 → 6 → 1 → 0

  问题描述,给定矩阵m如上面所示,从左上角开始每次只能向右或者向下走,最后到达右下角,求最小路径和。

  假设矩阵m的大小为M×N,首先生成大小和m一样的矩阵dp,dp[i][j]的值表示从开始位置(0,0)走到(i.j)位置的最小路径和。

  对于dp矩阵的第一行和第一列来说,如果要走到第一行的第3个,那么必定要经过第一行的第2个,也就是说第一行和第一列就是m[0][0...j]和m[0...j][0]这些值累加的结果,因此dp矩阵现在为上图的第2个矩阵。

  除了第一行和第一列之外,对于所有的(i,j),其前一步要么是(i-1,j)要么是(i,j-1),也就是说要想到达(i,j),必定经过(i-1,j)或(i,j-1),为了求最短的路径和,对于除了第一列和第一行之外的点,只需要使得dp[i][j] = min{dp[i][j-1], dp[i-1][j]} + m[i][j]即可,然后dp矩阵变成了上图中的第3个矩阵。

  所以,要求得最短路径和,就是dp矩阵的右下角的值,即为12。

  1.使用二维数组(时间复杂度O(M×N),空间复杂度O(M×N))求矩阵的最小路径和的解法

    public int minPathSum1(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) return 0;
int row = m.length, col = m[0].length;
int[][] dp = new int[row][col];
dp[0][0] = m[0][0];
for (int i = 1; i < row; i++) dp[i][0] = dp[i - 1][0] + m[i][0];
for (int j = 1; j < col; j++) dp[0][j] = dp[0][j - 1] + m[0][j];
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + m[i][j];
}
}
return dp[row-1][col-1];
}

  2.使用空间压缩方法利用一维数组(时间复杂度O(M×N),空间复杂度O(min{M,N}))求矩阵的最小路径和的解法。

  这道题可以使用一维数组来对空间复杂度进行优化,也就是不使用M×N的数组,仅仅使用长度为min{M,N}的数组即可。

  首先生成长度为4的数组arr,初始化arr={0,0,0,0},到达第一行的每个位置和dp一致,也就是先设置成arr={1,4,9,18},此时的arr[j]的值就代表从(0,0)位置到达(0,j)的最小路径和。

  然后,如果想把arr[j]更新成从(0,0)到(1,j)的话,也就是arr[0]代表从(0,0)到(1,0)的最短路径和,此时只需要arr[0] = arr[0]+m[1][0]=9即可,而arr[1]代表从(0,0)到(1,1)的最短路径和,此时从(0,0)到(1,1)有两种选择,一种是从(1,0)到(1,1),一种是从(0,1)到(1,1),前者可以用arr[1]+m[1][1]表示,而后者则可以用arr[0]+m[1][1]表示,因此arr[1]= min{arr[0],arr[1]} + m[1][1],同理,arr[2] = min{arr[1],arr[2]}+m[1][2],第二行更新完成后,arr为{9,5,8,12},也就是dp矩阵的第二行。

  接着,不断重复上面的步骤,让arr数组依次变成dp矩阵的每一行,最后变成了最后一行,取最后一个数即可。

  如果给定的M>N,同样可以令数组长度等于N,然后从左往右,令arr数组依次变为dp矩阵的每一列即可,这样就可以保证空间复杂度为O(min{M,N})。

    public int minPathSum2(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) return 0;
int more = Math.max(m.length, m[0].length);
int less = Math.min(m.length, m[0].length);
boolean rowmore = more == m.length; // 如果行数大于列数,rowmore为true
int[] arr = new int[less];
arr[0] = m[0][0];
// 如果行数大于列数,那么arr为dp的第一行,如果行数小于列数,那么arr为dp的第一列
for (int i = 1; i < less; i++) arr[i] = arr[i - 1] + (rowmore ? m[0][i] : m[i][0]);
// 如果行数大,arr已经是第一行,然后每一行遍历;如果列数大,arr已经是第一列,然后没一列遍历
for (int i = 1; i < more; i++) {
// 如果行数大,那么新arr[0]表示原arr[0]向下走一步;如果行数大,那么新arr[0]表示原arr[0]向右走一步
arr[0] = arr[0] + (rowmore ? m[i][0] : m[0][i]);
for (int j = 1; j < less; j++) {
// 如果行数大,那么i表示行,于是[i][j];如果列数大,那么i表示列,于是[j][i]
arr[j] = Math.min(arr[j - 1], arr[j]) + (rowmore ? m[i][j] : m[j][i]);
}
}
return arr[less - 1];
}

  3.总结

  压缩空间的方法几乎可以应用到所有需要二维动态规划的题目中,通过一个数组滚动更新的方式无疑节省了大量的空间。没有优化之前,取得某个位置动态规划值的过程是在矩阵中进行两次寻址,优化后,这一过程只需要一次寻址,程序的常数时间也得到了一定程度的加速。但是空间压缩的方法时有局限的,如果题目改成“打印具有最小路径和的路径”,那么就不能使用空间压缩的方法。因为空间压缩的方法是滚动更新的,会覆盖掉之前求解的值,让求解轨迹变得不可回溯。

  二、换钱的最少货币数

  1.题目:给定数组arr,且arr中所有值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张。给定一个整数aim代表要找的钱数,求组成aim的最小货币,钱不能找开的情况下默认返回-1。例如arr={5,2,3},如果aim=20,就返回4,因为货币数最少。

  (1)经典动态规划方法

  如果arr的长度为N,则生成行数为N、列数为aim+1的动态规划表dp。dp[i][j]的含义是:在可以使用任意张arr[i]货币的情况下,组成j所需的最小张数。

  步骤1,dp[0...N-1][0](dp矩阵的第一列)表示要找的钱数为0时,所需要的张数,即完全不需要货币,因此全设为0。

  步骤2,dp[0][0...aim](dp矩阵的第一行)表示只能使用arr[0]货币的情况下,找某个钱的最小张数。例如,假设arr[0]=2,那么dp[0][2]=1,dp[0][4]]=2,dp[0][6]=3...其他位置均找不开,所以设置为Integer.MAX_VALUE

  步骤3,剩下的位置依次从左到右,再从上往下计算。假设计算到位置(i,j),dp[i][j]的值可能来自下面的情况

  • 完全不使用当前货币arr[i]情况下的最小张数:即dp[i-1][j]的值
  • 只使用1张当前货币arr[i]情况下的最小张数:即dp[i-1][j-arr[i]]+1
  • 只使用2张当前货币arr[i]情况下的最小张数:即dp[i-1][j-2*arr[i]]+1
  • 只使用3张当前货币arr[i]情况下的最小张数:即dp[i-1][j-3*arr[i]]+1
  • ...

  也就是要求min{dp[i-1][j],min{dp[i-1][j-arr[i]]+1,dp[i-1][j-2*arr[i]]+2, dp[i-1][j-3*arr[i]]+3 ... dp[i-1][j-x*arr[i]]+x}(x>=1)}

  即dp[i][j] =min{dp[i-1][j-q*arr[i]] + q (q>=0)} min{dp[i-1][j], min{dp[i-1][j-x*arr[i]+x(x>=1)}

  令x=y+1,则dp[i][j] = min{dp[i-1][j], min{dp[i-1][j-arr[i] - y*arr[i] + y + 1(y>=0)}而这里因为dp[i][j] =min{dp[i-1][j-q*arr[i]] + q (q>=0)} 所以

  令j=j-arr[i],得到dp[i][j] = min{dp[i-1][j], min{dp[i-1][j-arr[i] - y*arr[i]] + y + 1(y>=0)}即dp[i][j] = min{dp[i-1][j], dp[i][j-arr[i] + 1}

  所以,最终由dp[i][j] = min{dp[i-1][j], dp[i][j-arr[i]+1},如果j-arr[i]<0,则说明arr[i]太大,用一张arr[i]后都会超过j,此时令dp[i][j]=dp[i-1][j]即可。

  经典动态规划算法(时间复杂度和空间复杂度都是O(N×aim))

  

  (2)

  2.题目:给定数组arr,且arr中所有值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用张。给定一个整数aim代表要找的钱数,求组成aim的最小货币,钱不能找开的情况下默认返回-1。例如arr={5,2,5,3},如果aim=10,就返回2,因为货币数最少。

  

  三、换钱的最少方法数

  

  

  

OptimalSolution(1)--递归和动态规划(2)矩阵的最小路径和与换钱的最少货币数问题的更多相关文章

  1. [程序员代码面试指南]递归和动态规划-换钱的最少货币数(DP,完全背包)

    题目描述 给定arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,求组成aim的最少货币数. 解题思路 dp[i][j]表示只用第0 ...

  2. OptimalSolution(1)--递归和动态规划(3)数组和字符串问题

    一.最长递增子序列(LIS) 给定数组arr,返回arr的最长递增子序列.例如,arr={2,1,5,3,6,4,8,9,7},返回的最长递增子序列为{1,3,4,5,8,9} 1.时间复杂度为O(N ...

  3. OptimalSolution(1)--递归和动态规划(1)斐波那契系列问题的递归和动态规划

    一.斐波那契数列 斐波那契数列就是:当n=0时,F(n)=0:当n=1时,F(n)=1:当n>1时,F(n) = F(n-1)+F(n-2). 根据斐波那契数列的定义,斐波那契数列为(从n=1开 ...

  4. OptimalSolution(1)--递归和动态规划(4)其他问题

    一.汉诺塔问题(包括chapter 1中的汉诺塔问题) 二. 三. 四. 五. 六. 七. 八.

  5. [DP]矩阵的最小路径和

    题目 给定一个矩阵m, 从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的树子累加起来就是路径和,返回所有的路径中最小的路径和. 解法一 这是一道经典的动态规划题,状态转移方程为d ...

  6. 70. Climbing Stairs【leetcode】递归,动态规划,java,算法

    You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb ...

  7. 【CF1151F】Sonya and Informatics(动态规划,矩阵快速幂)

    [CF1151F]Sonya and Informatics(动态规划,矩阵快速幂) 题面 CF 题解 考虑一个暴力\(dp\).假设有\(m\)个\(0\),\(n-m\)个\(1\).设\(f[i ...

  8. 【BZOJ5298】[CQOI2018]交错序列(动态规划,矩阵快速幂)

    [BZOJ5298][CQOI2018]交错序列(动态规划,矩阵快速幂) 题面 BZOJ 洛谷 题解 考虑由\(x\)个\(1\)和\(y\)个\(0\)组成的合法串的个数. 显然就是把\(1\)当做 ...

  9. 【BZOJ4870】组合数问题(动态规划,矩阵快速幂)

    [BZOJ4870]组合数问题(动态规划,矩阵快速幂) 题面 BZOJ 洛谷 题解 显然直接算是没法做的.但是要求的东西的和就是从\(nk\)个物品中选出模\(k\)意义下恰好\(r\)个物品的方案数 ...

随机推荐

  1. 2018年蓝桥杯java b组第四题

    标题:测试次数 x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机.各大厂商也就纷纷推出各种耐摔型手机.x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许 ...

  2. JAVA知识点总结(五)(常用类)

    第十八章:常用类 一.main方法解读: public static void main(String[] args) //当点击运行时,JVM会自动调用main方法 //public: 被JVM调用 ...

  3. 深度学习论文翻译解析(五):Siamese Neural Networks for One-shot Image Recognition

    论文标题:Siamese Neural Networks for One-shot Image Recognition 论文作者: Gregory Koch   Richard Zemel Rusla ...

  4. java架构之路-(分布式)初识zookeeper安装与参数详解

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  5. spring bean的生命周期与springmvc的生命周期

    配置在Spring中的Bean在Spring容器中从加载到销毁会经历那些过程呢?如果实现一些特定的Spring接口,这些特定接口的方法会在什么时候被调用呢?本文简单介绍一下这些过程. Bean在Spr ...

  6. Excel VBA入门(十)用户窗体开发

    VBA 中的用户窗体就是指带 UI 的用户界面,在运行的时候会单独弹出一个窗口,类似于在 windows 系统中运行的一个可执行程序一样(这个说法不太严谨,因为可执行程序也可能是只有命令窗口而没有 U ...

  7. 阿里云centos6.9搭建fastDFS文件服务器

    准备压缩包: 1.fastdfs-nginx-module_v1.16.tar.gz 2.FastDFS_v5.05.tar.gz 3.libfastcommonV1.0.7.tar.gz 4.ngi ...

  8. 【ADO.NET基础-Regidter】简单的账户注册界面和源代码(可用于简单面试基础学习用)

    在阅读时如有问题或者建议,欢迎指出和提问,我也是初学者......... 前台代码: <!DOCTYPE html> <html xmlns="http://www.w3. ...

  9. 深入Dapper.NET源码 (文长)

    目录 前言.目录.安装环境 Dynamic Query 原理 Part1 Dynamic Query 原理 Part2 Strongly Typed Mapping 原理 Part1 : ADO.NE ...

  10. .netcore+vue+elementUI 前后端分离---支持前端、后台业务代码扩展的快速开发框架

    框架采用.NetCore + Vue前后端分离,并且支持前端.后台代码业务动态扩展,框架内置了一套有着20多种属性配置的代码生成器,可灵活配置生成的代码,代码生成器界面配置完成即可生成单表(主表)的增 ...