上一篇文章上提到硬币找零的例子,现在我们实战动态规划就从硬币找零开始

问题描述:

给定 n 种不同面值的硬币,分别记为 c[0], c[1], c[2], … c[n],同时还有一个总金额 k,编写一个函数计算出最少需要几枚硬币凑出这个金额 k?每种硬币的个数不限,且如果没有任何一种硬币组合能组成总金额时,返回 -1。

这里我们先回忆一下动态规划问题的处理过程:

我们处理动态规划问题的时候需要分为这么几步:

1)确定初始化状态,初始化状态作为整个求解链路的原点,需要优先明确;

2)状态参数,中间状态在一步一步推导出最终状态的过程中会发生变化的变量;

3)明确决策方式,即:如何通过前面的状态推导出后面的状态;

4)中间状态存储,子问题存在大量重复计算的情况,我们将中间状态存储入“备忘录”。

确定初始化状态和状态参数

原文中明确指定出了硬币的面值(多个固定值),没有限制硬币的总个数(这是我们需要求解的结果),因而这两个无法作为我们的状态参数,那么状态参数只剩下的总金额k了。

显而易见,我们可以得出结论初始化状态是总金额为0的状态

决策方式

原文要求的是使用硬币凑出金额k,那么在我们每挑出一个硬币的时候就会改变状态,这就是这道题目的决策方式

中间状态的存储

当我们需要求解k=11的时候,k=11-c[n] 会被计算,以此类推,因而我们需要有一个中间状态的决策表存储子问题的解.

状态转移方程

现在我们可以尝试得出状态转移方程

      -1 (k<0) 异常情况也考虑下

f(k)= 0 (k=0)
min(1+f(k-c[i])) 0<i<n

到这里,感觉我们几乎可以直接写出代码了,别急,如果只是盲目的根据状态转移方程编写程序,那程序中依然存在着许多重复计算的子问题

可能从第一眼我们看这就是一个递归逻辑,子问题的答案是在不断向上返回,为什么还存在子问题的重复计算,其实我们可以展开来思考下这个程序,画个图方便理解重复计算出现在哪里

假定硬币面值是1,3,5 ,目标总金额是11

如图所示:由于递归是自顶向下的一个求解过程,F(7),F(5)等等在不同的分支中都会被重复计算,有一个解决方法就是将子问题缓存到中间表中,这样我们每遇到一个子问题,就到中间表中去尝试获取已知解,代码如下所示:

public int collect(int[] coins, int target) {Map<Integer, Integer> cache = Maps.newHashMap();int result = recursionCollect(coins, target, cache);return result==Integer.MAX_VALUE?-1:result;}private int recursionCollect(int[] coins, int target, Map<Integer, Integer> cache) {if(target<0){return Integer.MAX_VALUE;}if(target == 0) {return 0;}if(cache.getOrDefault(target, 0)!=0) {return cache.getOrDefault(target, 0);}int result = Integer.MAX_VALUE;for(int coin: coins) {int currentValue = recursionCollect(coins, target-coin, cache);if(currentValue<result) {result = currentValue+1;}}cache.put(target, result);return result;}

如代码所示,创建了中间缓存map,将子问题的解缓存到map中,这样保证当我们需要即将做重复计算的时候,通过缓存判断可以立即得到值。

那么,还能换一个方式吗?

在回答这个问题前,可以先考虑一下为什么要换一个方式?

前文说过,递归本身是一个自顶而下的一个过程(如求解的树结构图所示),虽然在上面的代码中,我们加了缓存,但是实际上缓存key对应的那个分支依然还是走了,只是这个分支的子分支被我们避开了。

现在只是3种硬币,如果种类更多那么分支的情况就更多,那我们可以预测到,还会有更多直接命中缓存的分支会出现。

作为一个爱钻牛角尖的程序员,我们可以考虑一下,是不是可以采用其他方式来规避这个问题。

我们要规避这种直接命中缓存的分支,其实就是希望不要每次都去判断这个子问题是否已经解决。

不希望判断,那就最好能提前确保子问题已经解决了,并且能快速的拿到子问题的解。

如何保证子问题一定提前解决了,那就是改变方向,由自顶向下改为自底向上

自底向上就是先从子问题开始,逐步向上求解更大的问题,这就可以考虑使用迭代(从最小值到目标值)取代递归。

基于这个思路,我们可以将代码做一下调整

public int collectNew(int[] coins, int target) {int[] dp = new int[target+1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0]=0;for(int sum = 1; sum <= target; sum++) {for(int coin:coins) {if(sum<coin) {continue;}dp[sum] = Math.min(dp[sum], dp[sum-coin]+1);}}return dp[target]==Integer.MAX_VALUE?-1:dp[target];}

我们来简单分析一下,第二段代码与第一段代码之间对比,提高在了哪里:

首先每一次循环都包含一个小循环,这个小循环会遍历所有的面值,这一点跟前面的迭代一致,

小循环内先看当前面额总值是否小于当前硬币面额。如果是,说明组合不存在,直接进入下一轮循环(这一步在递归中也能够做到)

当到选取硬币的地方,这里没有任何判断,直接比较最小值,这一步比递归那边少去了一层调用,因为我们是从最小值开始逐步向上求解,因而可以确信子问题在当前问题之前肯定已经得出了解

到这里,我们可以算是使用动态规划完美地解决了硬币找零问题。

我们再回顾一下解题思路

  1. 找到初始化状态,初始化状态其实就是迭代的起点,或者递归的终点。

  2. 明确问题的状态参数,这个状态参数其实就是在子问题与原问题之间变化的变量。

  3. 找到决策方式,状态参数在子问题与原问题之间变化的方式。

  4. 结合一下缓存表,解决子问题的重复计算。

基本上做到这些,状态转移方程我们就可以顺利地写出来了。

关于硬币找零问题就分享到这里,后续动态规划实战,会接着从背包问题开始逐步切入,有兴趣的小兄弟可以持续关注下

如果觉得本文有帮助可以分享给自己的小伙伴们!邀请他们关注下博主的微信公众号

小哥爱code

动态规划实战--硬币找零问题相关推荐

  1. java动态规划凑硬币问题,详解动态规划最少硬币找零问题--JavaScript实现

    硬币找零问题是动态规划的一个经典问题,其中最少硬币找零是一个变种,本篇将参照上一篇01背包问题的解题思路,来详细讲解一下最少硬币找零问题.如果你需要查看上一篇,可以点击下面链接: 详解动态规划01背包 ...

  2. 动态规划解决硬币找零问题

    题目描述:给定不同面额的硬币 coins 和一个需要找零的金额 n.编写一个函数来计算可以凑成金额n所需的最少的硬币个数.并求出所需硬币的所有面额并输出.(硬币可重复多次使用) 动态规划算法通常用于求 ...

  3. 动态规划——最少硬币找零问题(python)

    1. 问题描述 2. 思路 刚开始是想利用贪心算法.假如要找的零钱总额为49元,先找10块的,可以找40块,再继续找5块的,可以找5块,再继续找2块的,可以找4块.找够49元的最少纸币数为4 + 1 ...

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

    找零的两种问题 硬币找零问题,有两种.一种用贪心解决,一种用动态规划解决. 问题1:假设我们有 v1,v2,--,vn(单位是元)这些币值的硬币,它们的张数分别是 c1.c2.-, cn.我们现在要用 ...

  5. 硬币找零问题,动态规划基础,百度面试题

    问题描述:给出几种面值的硬币,要求用这几种硬币找零出所给零钱数,用的硬币数要最少. 过去我们用过贪心法解决此类问题,包括本人在百度面试时,也是用的贪心法(面试官对这个解答不满意),贪心法只适用于硬币特 ...

  6. 硬币找零问题的动态规划实现

    一,问题描述 给定一组硬币数,找出一组最少的硬币数,来找换零钱N. 比如,可用来找零的硬币为: 1.3.4   待找的钱数为 6.用两个面值为3的硬币找零,最少硬币数为2.而不是 4,1,1 因此,总 ...

  7. Python 动态规划(DynamicProgramming)-硬币找零

    动态规划(DynamicProgramming)-硬币找零 文章目录 动态规划(DynamicProgramming)-硬币找零 1.动态规划 a.什么是动态规划 b.适用对象 2.硬币找零-Codi ...

  8. 动态规划——硬币找零和币值最大化问题

    一.硬币找零问题 1.问题 有面值为1元.3元和5元的硬币若干枚,给定一个输入面额,问如何采用最少的硬币数目,得到当前面额 2.思路 找出状态转移方程,每次可以拿取1元.3元或者5元的硬币,每次拿取, ...

  9. 最少硬币找零系列问题(01背包,完全背包,多重背包动态规划)

    背包问题思路解决最小硬币找零系列问题. 一.01硬币找零问题(01背包) 给定不同面额的硬币 coins 和总金额 m.每个硬币最多选择一次.计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬 ...

最新文章

  1. Java入门培训班怎么选择
  2. labuladong的算法小抄pdf_推荐两个学算法的 GitHub 项目
  3. js-jQuery对象与dom对象相互转换
  4. mysql 获取数据列号_如何获得mysql数据库的所有的列
  5. 《软件工艺师:专业、务实、自豪》一3.7.2 软件工艺概念走向全球
  6. AttributeError: 'str' object has no attribute 'decode' django问题
  7. vm显示打不开 /dev/vmmon:Broken pipe
  8. jQuery 处理xml
  9. 【cmd】日期、时间格式化
  10. 系统学习机器学习之神经网络(七) --CPN
  11. CF1427F Boring Card Game
  12. Android应用开发-小巫CSDN博客客户端总结篇
  13. unity ar vr_学习在Unity中创建AR和VR应用
  14. 2021-08-15nginx访问502,日志报错:connect() to 127.0.0.1:180 failed (13: Permission denied)解决
  15. HTML创建表格及合并单元格
  16. 超详细: Type-C接口Macbook笔记本无法充电(时连时断)的傻瓜处理流程
  17. GBASE 8C——SQL参考 5 全文检索
  18. Matlab 斜率和曲率,曲率_与闪电共舞_新浪博客
  19. 安装gensim失败,各种方法未果,最后注意到 error: Microsoft Visual C++ 14.0 or greater is required,终于解决
  20. 猿创征文 | Shell编程【上篇】

热门文章

  1. vue cli更换版本
  2. 铁死亡研究丨mTORC1 抑制剂解决方案
  3. 自己造轮子--线性回归实现遇到的坑
  4. 小机器人在现实世界中学会快速驾驶
  5. nginx配置websocket
  6. python_opencv实现图像分割(多分类彩色图像)孔洞填充后处理
  7. 软件设计师七(CPU组成)
  8. 汽车鼓式制动器设计(论文 CAD图纸 开题报告 任务书 答辩PPT 文献翻译)
  9. windows编程入门之句柄 spy++
  10. Spring Boot + BPMN流程管理引擎实践