leetcode 322 零钱兑换问题
Leetcode 322题 零钱兑换问题
题目及实例
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
1
2
3
4
5 示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
1
2
3
4 示例 2:
输入: coins = [2], amount = 3
输出: -1
这个题目乍一看像是贪心算法,但是贪心算法容易陷入局部最优,如coins=[1,2,7,10],全局最优解应为[7,7],但是谈心会陷入局部最优解[2,2,10];
零钱问题曾经是我大二算法课考试的题目,一般会采用动态规划的解题思路,但是在leetcode的题解中看到了曾经我觉得性能可能会很差的回溯(+剪枝)的方法竟有很不错的性能,给了我很大的启发,所以下面我回把两个方法都介绍一边
动态规划
解析
动态规划其实是一个填表的过程,数组dp是一个长度为amount+1的数组(为什么要加1,因为钱数从0到amount有amount+1个数),其中第i个元素代表如果需要找i块钱需要的最少硬币数,这样一直算到最后,dp[amount]就是我们需要的答案。
那怎么保证从前往后的每一步都能得出当前的最少数目呢
首先我们把数组都初始化成一个最大值(这里用的是amount+1,因为数组中的元素不可能比amount大)
第一个元素当需要找的钱为0时,个数肯定是0
当找零数为i时,进行以下操作
1 | for i:1 to coins.size() do |
其实就是遍历coins列表,找到可以使当前数最小的值,dp[i]是原始值,代表不使用coins[j]这个硬币,dp[i - coins[j]] + 1代表使用这个硬币,计算里面的最小值,这样就保证了我每一个dp[i]都会是全局最优的情况,所以最后的结果也是全局最优
动态规划代码
1 | class Solution { |
回溯+剪枝
解析
这个算法只有回溯的话性能会很差,但是加上剪枝操作后会带来很多的性能提升,在提交中也取得了很好的结果,给了我很大的启发,这里要感谢leetcode上的Ikaruga的思路和代码
这个就是很简单的思想,我一个一个试,如果不成就回溯,如果成了就记录下当前的方案的结果,用一个变量记录全局的最优质的,每一个结果都会跟当前的最好的值比较,如果新得出的结果最好就更新全局的值
但是问题在于这样其实是很耗时的,如果我很早的就得出了最优的值那我后面还会做很多无谓的操作,这里剪枝的用处就出现了,下面的代码中用 k + count < ans 这个判断来进行了剪枝的操作,如果当前的数目已经没有成为最优解的可能性了,就不会再往下进行搜索。
这样最好情况下很快就能完成遍历,这其实就是一种贪心的思想,这也是提交结果很好的原因,因为很多情况下贪心算出的局部最优解就是全局最优。但是最坏情况下需要遍历所有的可能性,时间也会比动态规划长。
代码
1 | class Solution { |