一,问题描述
给定一组硬币数,找出一组最少的硬币数,来找换零钱N。
这类问题由于给定的硬币面值与数量的不同,可能演化出很多种不同的版本,这里先讲最简单的两种形式。
二,贪婪法求解硬币找零问题
贪婪法的思路很简单,不断地从总找零值里减去面值最大的硬币。如果找零的值小于最大的硬币值,则尝试第二大的硬币,依次类推。
C++代码实现如下:
1 #include <iostream> 2 3 using namespace std; 4 5 int main() 6 { 7 int coin_num, N; //硬币面值数量,需要找零的钱数 8 cin>>coin_num>>N; 9 int coin[coin_num];//不同的硬币面值 10 for (int i = 0; i < coin_num; ++i) 11 cin>>coin[i]; 12 13 int total_num = 0, cur_coin = coin_num - 1; 14 while (N!=0) 15 { 16 total_num += N / coin[cur_coin]; //从最大的硬币面额开始,从总钱数里面扣除 17 N %= coin[cur_coin]; 18 cur_coin--; 19 } 20 cout<<total_num<<endl; 21 }贪婪法实现硬币找零
但是使用贪婪法求解硬币找零问题,必须要让给定的硬币面值满足一定的先决条件,否则求出的解不是最优解!
正例:
给定一组硬币:1,5,10,20。N=30。求最小硬币数。
答案显然是2,因为先考虑25,剩5,取一个5元。最后结果为2。
考虑如下一个例子:
给定一组硬币:1,5,20,25。N=40。求最小硬币数。
如果用贪婪法,先考虑25,剩余15,取3个5元。最后结果为4。
但是很显然,如果直接使用20的硬币,则2个即可。由此可见,贪婪法的解在这个问题里面并不是最优的。对于这种情况,就需要使用动态规划进行求解。
而使用贪婪法能够求出最优解的条件是:每个大的面值,都是小的面值的整数倍。
从上面的正例来看, 20是10,5,1的整数倍,同理10是5,1的整数倍,以此类推。而在反例中,25不是20的整数倍。
证明方法可以见这篇博客,博主的证明非常详细。
贪婪算法硬币找零最优解问题证明:https://www.cnblogs.com/organic/p/6151702.html
而在这里我们只体会一下原因:只有在大的面额是小的面额的整数倍的情况下,先找大的金额才一定时最优的。例如20是10的倍数,则所有能用20抵扣的金额,都一定比用10来抵扣花的数量少。
反之则不一定。例如25和20,在凑40时,两个20即可,而25还需要和更小的金额搭配。
二,动态规划求解硬币找零问题
根据动态规划的思想,我们将给N找零的问题分解成“给更小金额找零”的子问题。最后,利用小面值目标的解,去得出大面值目标的解。具体实现,首先是先从小面值的找零开始,将解决方案依次存入动态规划数组,一直往大面值累加,最后直接输出动态规划数组指定位置的值也即大面值找零所需的最少硬币数,算法实现如下:
int coinChange(vector<int>& coins, int amount) { int n = coins.size(); if (n==0) return -1; if (amount == 0) return 0; int coin_num[amount+1] = {0}; for (int i = 0; i < n; ++i) { if (coins[i] <= amount) coin_num[coins[i]] = 1; } for (int i = 1; i <=amount; ++i) { for (int j = 0; j < n; ++j) { if (i - coins[j] >=0 && coin_num[i-coins[j]] > 0) { if (coin_num[i] == 0) coin_num[i] = coin_num[i-coins[j]] + 1; else coin_num[i] = min(coin_num[i], coin_num[i-coins[j]] + 1); } } } if (coin_num[amount] == 0) return -1; else return coin_num[amount]; }动态规划实现硬币找零