题目:给你k
种面值的硬币,面值分别为c1, c2 ... ck
,每种硬币的数量无限,再给一个总金额amount
,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。
输入:
3 11
1 2 5
输出:3
1、暴力递归版本(自顶向下)
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 101;
int coinsChange(int coins[], int coinClass, int amount)
{
if (amount == 0)
return 0;
int res = INT_MAX;
for (int i = 0; i < coinClass; ++i)
{
if (amount - coins[i] < 0)
continue;
res = min(res, 1 + coinsChange(coins, coinClass, amount - coins[i]));
}
return res;
}
int main() {
int coinClass, amount;
cin >> coinClass >> amount;
int coins[maxn];
for (int i = 0; i < coinClass; ++i)
{
cin >> coins[i];
}
cout << coinsChange(coins, coinClass, amount) <<endl;
system("pause");
}
2、带备忘录的递归(自顶向下)
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 101;
//int dp[maxn] = { INT_MAX }; 只能直接用0初始化,其他情况用fill或memset
int dp[maxn];
int coinsChange(int coins[], int coinClass, int amount)
{
if (amount == 0)
return 0;
int res = INT_MAX;
dp[0] = 0;
for (int i = 0; i < coinClass; ++i)
{
if (amount - coins[i] < 0)
continue;
if (dp[amount - coins[i]] != INT_MAX) {
res = min(res,dp[amount - coins[i]] + 1);
}
else {
res = min(res, 1 + coinsChange(coins, coinClass, amount - coins[i]));
}
dp[amount] = res;
}
return res;
}
int main() {
fill(dp, dp + maxn, INT_MAX);
int coinClass, amount;
cin >> coinClass >> amount;
int coins[maxn];
for (int i = 0; i < coinClass; ++i)
{
cin >> coins[i];
}
cout << coinsChange(coins, coinClass, amount) << endl;
system("pause");
}
3、dp数组的迭代解法(自底向上)
int coinsChange(int coins[], int coinClass, int amount)
{
if (amount == 0)
return 0;int res = INT_MAX;
dp[0] = 0;
for (int i = 0; i < coinClass; ++i)
{
if (amount - coins[i] < 0)
continue;
if (dp[amount - coins[i]] != INT_MAX) {
res = min(res,dp[amount - coins[i]] + 1); //result用于记录三种选择结果的最小值
}
else {
res = min(res, 1 + coinsChange(coins, coinClass, amount - coins[i]));
}
dp[amount] = res;
}
return res;}
针对带备忘录的递归进一步简化:首先因为dp[amount]=result,故可用dp[amount]替换res
int coinsChange(int coins[], int coinClass, int amount)
{
if (amount == 0)
return 0;int dp[amount] = INT_MAX;
dp[0] = 0;
for (int i = 0; i < coinClass; ++i)
{
if (amount - coins[i] < 0)
continue;
if (dp[amount - coins[i]] != INT_MAX) {
dp[amount] = min(dp[amount],dp[amount - coins[i]] + 1);
}
else {
dp[amount] = min(dp[amount], 1 + coinsChange(coins, coinClass, amount - coins[i]));
}
}
return dp[amount];}
其次我们进行自底向上的递推,那么每一项未知项都是由已知项推出的,针对这个情况可以进一步简化:
int coinsChange(int coins[], int coinClass, int amount)
{
if (amount == 0)
return 0;int dp[amount] = INT_MAX;
dp[0] = 0;
for (int i = 0; i < coinClass; ++i)
{
if (amount - coins[i] < 0)
continue;
dp[amount] = min(dp[amount],dp[amount - coins[i]] + 1);
}
return dp[amount];}
函数coinsChange 是计算金额为amount所需最少硬币数,也即返回值dp[amount],若要计算给定金额AMONT,则需要自底向上由已知dp[0]一步步递推出dp[AMOUNT],如下图:
这里的1+min(dp[4],dp[3],dp[0]和上文的 dp[amount] = min(dp[amount],dp[amount - coins[i]] + 1)作用相同。用双重循环实现:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 110; //maxn的值一定要大于amount
int dp[maxn];
int main() {
int n, amount, coins[3] = { 0 };
fill(dp, dp + maxn, INT_MAX);
cin >> n >> amount;
for (int i = 0; i < n; i++) {
cin >> coins[i];
}
dp[0] = 0;
for (int i = 1; i < maxn; i++) {
for (int j = 0; j < n; j++) {
if (i - coins[j] < 0)
continue;
dp[i] = min(dp[i], 1 + dp[i - coins[j]]);
}
}
cout << dp[amount];
system("pause");
return 0;
}