01 | 硬币找零问题:从贪心算法说起
硬币找零问题
- 深入了解
- 翻译
- 解释
- 总结
贪心算法是一种简单而有效的解决算法问题的方法,本文以硬币找零问题为例,介绍了贪心算法的基本思路和应用。文章首先强调了贪心算法的局部最优解和整体最优解的区别,然后通过具体问题的讲解,阐述了贪心算法在解决最值问题时的应用。贪心算法的局限性也在文章中得到了讨论,指出了纯粹采用贪心策略可能无法达到整体最优的问题。然而,贪心算法作为求解整体最优的思路源头,为其他优化方法提供了基础。在没有优化的情况下,穷举从来就不算是一个好方法。所以我带你使用了贪心算法来解题,它是一种使用局部最优思想解题的算法。但是通过硬币找零问题,我们也发现了贪心算法本身的局限性。在求解最值问题时,我们需要更好的方法来解。
《动态规划面试宝典》,新⼈⾸单¥59
全部留言(57)
- 最新
- 精选
- AshinInfo置顶重新调整得了java代码部分,提高代码的可读性 private static void getMinCoinCountOfValue() { // 硬币面值 int[] values = {5, 3}; // 总价 int total = 11; int minCoinCount = getMinCoinCountOfValueHelper(total, values); // 输出结果 System.out.println(minCoinCount); } /** * @param total 金额 * @param coins 币种数组,从大到小排序 * @return 返回币数,如果返回-1表示无法凑够total */ private static int getMinCoinCountOfValueHelper(int total, int[] coins) { if (coins.length == 0) { return -1; } //当前币值 int currentCoin = coins[0]; //使用当前币值数量 int useCurrentCoinCount = total / currentCoin; int restTotal = total - useCurrentCoinCount * currentCoin; // 如果restTotal为0,表示余额已除尽,组合完成 if (restTotal == 0) { return useCurrentCoinCount; } // 其他币种数量 int coninCount = -1; // 剩余的币种 int[] restCoins = Arrays.copyOfRange(coins, 1, coins.length); while (useCurrentCoinCount >= 0) { // 否则尝试用剩余面值求当前余额的硬币总数 coninCount = getMinCoinCountOfValueHelper(restTotal, restCoins); // 如果后续没有有可用组合,退一步,当前useCurrentCoinCount币数减1 if (coninCount == -1) { // 否则尝试把当前面值数-1 useCurrentCoinCount--; // 重新计算restTotal restTotal = total - useCurrentCoinCount * currentCoin; } else { return useCurrentCoinCount + coninCount; } } return -1; }
作者回复: 赞,感谢你的分享,顶一顶,让更多人看到。
2020-09-161265 - sanyinchen上述回溯+贪心并不能取到最优解, 比如[1,7,10] amount=14 那么根据递归深搜 10 + 1 + 1 + 1 + 1 会比 7 + 7 先到
作者回复: 嗯,这里的代码逻辑需要再加上“回溯时比较保留最小值”,这样就没有问题了。 贪心真的很容易过于贪婪,代码已更新。
2020-09-16327 - 梅亮宏@创造力老师说的很生动!用递归加局部最优的方法一定能得到正解。但是如果问题变得更加复杂的情况下,例如我们有1亿中硬币可以用,总币值为几万亿。可能还需要优化一下算法性能或者用分布式计算把性能提高? 这让我想到了ai中的reinforcement learning。个人认为有些偏全局优化?就如alphaGo,每一步棋都需要以整个棋局为出发点考虑。有点像老师这个算法里面的递归,即每尝试走一步的时候都会记录状态,用树状结构罗列所有可能性然后回溯。通过神经网络来的weight值估算是不是应该回溯。另外即使找到最优解了,还可以设置exploit更多最优的可能。纯个人理解,有点发散,希望老师指正!
作者回复: 没错,寻找最优解的时候,本质上还是需要进行枚举(穷举),因此优化算法性能是最重要的第一步,如果使用了动态规划,就可以大幅压缩计算时间,在此基础上,如果性能仍不满意(即数据规模实在太巨大),那么则可以考虑使用工程化的方法,比如你提到的分布式计算来提高计算性能。 AI 中的强化学习,本质上它的目的是寻找全局最优解。但是,我们最终能找的只是一定范围内的局部最优解,使得这个局部最优解在一定条件下,最接近我们期望的全局最优解。这个发散点很好,算法发展到一定高度就会进入机器学习领域,无论如何,基本的算法思路其实没有本质差别。
2020-09-15210 - AshinInfo递归的目的是求解 回溯+递归的目的是枚举所有组合的解,并取最优解返回 没有回溯,递归只能获得一个解或者无解,获得的解不一定是最优解 递归是一种算法结构,回溯是一种算法思想 一般回溯多用递归实现
作者回复: 是的,回溯就相当于穷举过程中递归的一个补丁(patch)。 另外,你对递归和回溯的理解、比喻,比较恰当。可以这么理解。
2020-09-278 - Karl老师,第一段代码的第22行,是不是应该为调用GetMinCoinCountHelper?
作者回复: 你好 Karl! 感谢你提出此问题。代码已经做了相应调整和更新。并追加了 Java 版本的代码供大家参考。
2020-09-146 - 好运来测试在原有贪心基础上加上回溯可以找到一组可行解: int[] values = new int[] {5, 3}; // 硬币面值 int total = 11; // 总价 贪心策略求出可行解不是全局最优解: values = new int[]{5, 4, 1}; // 硬币面值 total = 13; // 总价
作者回复: 赞。事实上,纯粹的贪心算法只考虑了局部最优的情况,因此在绝大多数情况下是得不到最优解的。在回溯版本中,需要枚举所有的组合,并保留最小的结果。本质上,这里是通过回溯来进行穷举的,因此效率还是不够好。后面的课程会给出更好的解决方法。
2020-09-144 - EncodedStar对动规有了新的了解,感谢老师!
作者回复: 欢迎进入动归世界。 动态规划其实也不难,是有规可循的。跟随课程,我们一起进步。
2020-09-1424 - vinGetMinCoinCountLoop这个方法是在干嘛呀?感觉直接getMinCoinCountOfValue(total, values, 0)不就行了吗?GetMinCoinCountLoop将数组元素换来换去有啥意义吗?有点看不懂,要是多加点注释就好了
作者回复: 这里实际上是考虑所有的排列组合。
2021-08-183 - 020看来半天回溯+贪心,发现其实就是把所有情况都枚举出来取最小值。。。。。。
作者回复: 贪心本身其实是不想枚举出所有情况,不过这个就会导致只能得到局部最优解,最后想要解决问题还是要通过不同方式枚举出所有组合。
2021-05-193 - 宋不肥时隔1个多月,二刷一下,发现这一篇比第二篇代码难读,就在于变量和函数的命名,实在是又长,意义又差,比如valueCount 完全不如valuelength这样的名字来的直观,而且取名还很像,很多时候看了几行,就忘记标识符号的意思是啥来着了,第二篇命名改进了之后可读性就好很多。
作者回复: 谢谢建议,变量命名可读性这个因人而异,的确不能满足所有人的命名习惯,我会考虑改进的。
2021-01-272