1.简介:

在leetcode上刷题的时候,遇到了一道找零钱的动态规划题,后台测试用例很变态,必须把算法优化的很好才能通过。也借此机会好好的研究了一下动态规划。在下小白一个,大神轻喷。

2.题目如下:

image.png

分析:我们可以从最简单的暴力搜索开始,先优化成记忆搜索方法,再到动态规划,最后再对动态规划进行空间优化。

1)最简单的思路就是用暴力搜索的方法,枚举每一种情况,累加求和,但存在大量重复计算。时间复杂度 > O((amount * coins.length)²)

2)进一步我们可以优化成记忆搜索方法,将每一个计算结果保存下来,用空间换时间,减少重复计算。时间复杂度 = O((amount * coins.length)²)

3)在记忆搜索的基础上,优化成动态规划,规划好路径,消除重复计算。和记忆搜索一样,用空间换时间。

时间复杂度O(amount * coins.length)

2.暴力搜索法

暴力搜索即用穷举法解决,穷举每一种可能再累加,基于示例1来看

1)结果 = 当不用coins[0]硬币时的组合数 +

使用1个coins[0]硬币时的组合数 +

使用2个coins[0]硬币时的组合数 +

...

使用5个coins[0]硬币时的组合数。

2)不使用coins[0]硬币 = 不使用coins[1]硬币 +

使用1个coins[1]硬币 +

使用2个coins[1]硬币+

...

对每个子项累加等于amount时返回1,否则0

可以用amount累减来代替累加

代码如下:

public void solveCutCoin(int[] coins, int amount){

System.out.println(codeCutCoin(coins, 0, amount));

}

//返回coinArr[index...N-1]种硬币的分割方法

public int codeCutCoin(int[] coins, int index, int amount){

//baseCase

int res = 0;

//如果是最后一种硬币,并且amount=0

if (index >= coins.length){

res = 0 == amount ? 1 : 0;

return res;

}

//用amount-硬币面值 代替累加。

for(int i = 0; i* coins[index]<= amount; i++){

res += codeCutCoin(coins, index+1, amount -i* coins[index]);

}

return res;

}

3.记忆搜索法

分析:记忆搜索方法就是将暴力法里的计算结果存储起来,用空间换时间的方法,需要进行以下两步修改:

1)方法中有两个变量index和amount,所以新建一个

int counts = new int[coins.length][amount],

为了方便边界处理,可以建[coins.length+1][amount+1]的。

counts[i][j]指用coins[i]...coins[n-1]这几种硬币,可以组成 j 金额的组合数

2)在取值的时候,先判断[index][amount]对应位置的值是否计算过,

(1)如果计算过,直接使用;

(2)没计算过,计算后将值记录下来。

Java中int数组默认值为0,为了区分是没计算过,还是组合数为0,我们将组合数为0的位置赋值-1,取用时判断是-1返回0。

代码如下:

//记忆搜索方法

public void solveCutCoin2(int[] coins, int aim){

int[][] count = new int[coins.length+1][aim+1];

System.out.println(codeCutCoin2(coins, 0, aim, count));

}

public int codeCutCoin2(int[] coins, int index, int amount, int[][] counts){

//baseCase

int res = 0;

if (index >= coins.length){

res = 0 == amount ? 1 : 0;

return res;

}

//当前位置值不为0,表示计算过,直接使用

if (0 != counts[index][amount]){

return (-1 == counts[index][amount]) ? 0 : counts[index][amount];

}

//计算当前位置的值

int count;

for(int i = 0; i* coins[index]<= amount; i++){

//先判断是否计算过

count = counts[index+1][amount -i* coins[index]];

if (0 != count){

res += (-1 == count) ? 0 : count;

}else {

//没计算过,重新计算

res += codeCutCoin2(coins, index+1, amount -i* coins[index], counts);

}

}

//将每次计算过的值保存,用-1标记组合数为0的位置

counts[index][amount] = (0 == res) ? -1 : res;

return res;

}

4.动态规划

分析:动态规划是将一个问题化简为子问题,通过子问题的最优解,组成整体最优,要理解整个过程,首先我们得画个图,分析一下暴力搜索的过程。

暴力搜索:

表格填充的数值为:只用coins[i]有多少种组合得到amount[j],非0即1.

第二行的取值为下面公式的值, 图1此时index=0,coins[index]=1

counts[index+1][amount -i* coins[index]];

为了帮助理解我特意画了两个图,示例1画出的图很多细节没法展示出来。

初始化

搜索完成后

上面两张图是从右上角点往下求的,由于解法和硬币面值顺序无关,我们同样可以从下往上求,图2初始index=2, coins[index]=5, amount=10

counts[index-1][amount -i* coins[index]];

如下图所示:

自下而上搜索

我们根据面两个图继续分析,已知:

总组合数:counts[i][j] = 使用0枚5元硬币:counts[i-1][j]

+使用1枚5元:counts[i-1][j-coins[i] * 1]

...

+使用n枚5元:counts[i-1][j-coins[i] * n ]

即枚举他前一排黄色格子累加

我们发现

counts[i][j-coins[i] * 1] = count[i-1][j-coins[i] * 1]

+counts[i-1][j-coins[i] * 2]

...

+counts[i-1][j-coins[i] * n]

所以

counts[i][j] = counts[i][j-coins[i] * 1] + counts[i-1][j]

将公式套到上面两个表格,结果成立。

至此,动态规划的思路已经出来了,上面的一大串确实很繁琐,但理解后还是很简单的。其实动态规划就是对暴力搜索的一种化简,使得计算更高效。

只要列举出可能性,解决了counts[i][j] = 什么 + 什么,大部分问题也就迎刃而解了。

代码实现:

//动态规划

public void solveCutCoin3(int[] coinArr, int aim){

int[][] counts = new int[coinArr.length][aim+1];

System.out.println(codeCutCoin3(coinArr, coinArr.length-1, aim, counts));

}

public int codeCutCoin3(int[] coins, int index, int amount, int[][] counts){

//baseCase

if (0 == amount){

return 1;

}

if (index < 0 || amount < 0){

return 0;

}

if (0 == counts[index][amount]){

int res = codeCutCoin3(coins, index-1, amount, counts)

+ codeCutCoin3(coins, index, amount - coins[index], counts);

counts[index][amount] = (0 == res) ? -1 : res;

}

return -1 == counts[index][amount] ? 0 : counts[index][amount];

}

再上一张动态规划的路径截图:

image.png

5.大神的解法

上面解法看似已经很好了,但是在大神眼里这什么都不是,leetCode评论区里的解法,时间复杂度O(coins.length * amount), 空间复杂度O(amount)

在绝对的实力面前,瑟瑟发抖。

我们来看看大神的解法:

image.png

6.谈一谈怎么写递归

很多人可能觉得递归函数很难写,事实也如此。递归函数要求每一步都高度一致,很难一步到位,所以不妨分成几个步骤,逐个突破。我写递归一般有4个步骤:

1)确定需要的信息,如组合数

2)写出BaseCase,即在什么情况下不需要递归,返回确定值

3)将递归函数当做黑盒使用

4)解黑盒,将需要的信息返回给上一个调用者

java 动态规划找零钱_初探动态规划——LeetCode找零钱问题相关推荐

  1. python动态规划详解_经典动态规划例题整理(Python版)

    由于本人的算法基础较为薄弱,所以在这里整理一下自己的做过的题,使自己能够随时随地回顾温习. 然后,本篇文章将会持续更新自己遇到的一些比较经典动态规划的题目,大家如果对代码有任何问题,直接在文章下面评论 ...

  2. Java动态规划走金字塔_【动态规划基础】数字金字塔

    1258:[例9.2]数字金字塔 时间限制: 1000 ms         内存限制: 65536 KB 提交数: 9635     通过数: 5467 [题目描述] 观察下面的数字金字塔.写一个程 ...

  3. java redis使用卡死_记一次找因 redis 使用不当导致应用卡死 bug 的过程

    原标题:记一次找因 redis 使用不当导致应用卡死 bug 的过程 作者:小木 my.oschina.net/xiaomu0082/blog/2990388 首先说下问题现象:内网sandbox环境 ...

  4. java代码实现画板_求好心人帮找或做个JAVA画板程序 代码,主要能实现简单的画板功能!...

    展开全部 取个叫pb.java的文件拷贝进去(pb类为main入口),直接运行 --e68a843231313335323631343130323136353331333236376538------ ...

  5. java不是关键字_以下( )不是Java的关键字。_学小易找答案

    [单选题]放大电路的工作方式有甲类.乙类和甲乙类等,其中甲乙类放大器中放大管的导通角 . [单选题]以下标识符中不合法的是( ). [填空题]完成一个对象的复制后,如果在数值控制框中输入( ),会在复 ...

  6. java有序数组找中位数_有序数组中找中位数

    题目:两个有序数组A和B,大小都是n,寻找这两个数组合并后的中位数.时间复杂度为O(logn). 中位数:如果数组的个数是奇数,那么中位数的值就是有序时处于中间的数:如果数组个数是偶数的,那么就是有序 ...

  7. java不能作为整型常量_以下字符组合中,不能作为Java整型常量的是_学小易找答案...

    [单选题]关于现浇水磨石面层的外观质量要求,以下说法不正确的是( ). [单选题]60.将/usr 文件打包为 usrback.tar.gz的方法是: [简答题]什么是产品组合?它包括什么?怎么理解产 ...

  8. java基础代码实例_全网都在找的Python简单基础小程序的实例代码

    这篇文章主要介绍了Python简单基础小程序的实例代码,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下. 1 九九乘法表 3 4 5 6 7for i in range(9):#从0循环到8 ...

  9. 在墙上找垂直线_墙上如何快速找水平线

    在装修房子的时候,墙面的面积一般都很大,所以在施工的时候要找准水平线很重要,那么一般施工人员是如何在墙上快速找水平线的呢?今天小编就来告诉大家几种找水平线的方法. 一.如何快速找水平线 1.用一根透明 ...

最新文章

  1. ESLint里的规则教会我,无规矩 不编程
  2. yii mysql 操作数据库_Yii数据库操作_MySQL
  3. 文档协同编辑帮助企业迎接大数据时代
  4. 信息检索报告_读者信息素养状况问卷调查分析报告来啦
  5. Webstorm 10.0.4 配置
  6. C++使用Json作为数据包装格式的通信
  7. 【Git】Python项目依赖库过大无法提交的问题
  8. asp sql 导出 excel_Mysql数据导出到excel基于python
  9. 内卷下,贷中监控对商户端如何规则
  10. CentOS 通过yum来升级php到php5.6
  11. Ubuntu环境下安装QT5
  12. 总结2018,规划2019
  13. 物联网应用技术学习内容及就业前景
  14. MTBD 电影市场分析
  15. 全国各省-土地转让收入(1995-2019年)
  16. nb服务器协议,nb-iot协议详解
  17. python下载网页上的文件_用Python下载一个网页保存为本地的HTML文件实例
  18. 程序员如何获得好绩效?
  19. 考虑柔性负荷的综合能源系统低碳经济优化调度论文复现——附代码
  20. 营业执照经营范围怎么填写

热门文章

  1. 程序员摘镜指南:近视手术能不能做?怎么做?
  2. 《通信技术导论(原书第5版)》——1.11 网络布线
  3. 蓝桥杯真题 19省2-年号字串 小明用字母 A 对应数字 1, B 对应 2,以此类推,用 Z 对应 26。对于 27以上的数字,小明用两位或更长位的字符串来对应,例如 AA 对应 27, AB
  4. 2016年终总结:我只想成为自己喜欢的人 不负己心 不负此生
  5. js判断当前手机的操作系统
  6. 大型企业中业务中台建设思考
  7. SSH三大框架笔面试总结
  8. 告别黑暗 电脑黑屏故障排除实例一则
  9. 雷达图 自定义点样式 渐变背景色
  10. 基于ATX自动化测试解决方案