一、矩阵的最小路径和

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. 【django】分页

    分页 1.简单分页 from django.conf.urls import url from django.contrib import admin from app01 import views ...

  2. Python基础(十六)

    今日主要内容 内置模块(标准库) 序列化 hashlib collections 软件开发规范 一.内置模块(标准库) (一)序列化模块 什么是序列化? 将一种数据结构(如列表.字典)转换为另一种特殊 ...

  3. 如果有人问你 JFinal 如何集成 EhCache,把这篇文章甩给他

    废话不多说,就说一句:在 JFinal 中集成 EhCache,可以提高系统的并发访问速度. 可能有人会问 JFinal 是什么,EhCache 是什么,简单解释一下. JFinal 是一个基于Jav ...

  4. Aria2 1.35.0,更新,测试,发布

    在上一篇: 有哪些便宜还好用的东西,买了就感觉得了宝一样? 结尾提到了Tatsuhiro Tsujikawa的aria2计划在10月更新一个新的版本 今天趁着雨后明月挂天,开始了简单的更新 虽然在半年 ...

  5. 从0开始学FreeRTOS-1

    我们知道,(单核)单片机某一时刻只能干一件事,会造成单片机资源的浪费,而且还有可能响应不够及时,所以,在比较庞大的程序或者是要求实时性比较高的情况下,我们可以移植操作系统.因为这种情况下操作系统比裸机 ...

  6. 04-04 AdaBoost算法代码(鸢尾花分类)

    目录 AdaBoost算法代码(鸢尾花分类) 一.导入模块 二.导入数据 三.构造决策边界 四.训练模型 4.1 训练模型(n_e=10, l_r=0.8) 4.2 可视化 4.3 训练模型(n_es ...

  7. 蓝牙TWS耳机IBRT的原理初分析

    最近在倒腾TWS对耳的一些东西,看到一些源码,发现一个新概念,IBRT没有搞清楚,抱着吾将上下而求索的态度,详细看了一些代码,查了一些资料,还是发现了不少有价值的信息的.至少,我突然感觉自己懂了一些什 ...

  8. DJango配置mysql数据库以及数据库迁移

    DJango配置mysql数据库以及数据库迁移 一.Django 配置MySQL数据库 在settings.py中配置 import pymysql # 配置MySQL pymysql.install ...

  9. vue中 props 多层组件嵌套传值

    如:三层嵌套. 父组件=>子组件=>孙子 1. 父组件引用子组件component11 , isShow传值给子组件component11 2. 子组件用  props 接受父组件的值, ...

  10. HTML5开发常见的7个框架,你知道几个?

    互联网的迅速发展,软件行业成了更多年轻人的就业选择.HTML5简单易学门槛低,是Web时代前端开发超好用的工具.而HTML5开发人员的就业薪资也远远高于其他行业. 资料显示,初级HTML5开发人员的平 ...