给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。
1. 思考
本题常规解法是利用动态规划。动态规划的题目,关键在于转移方程。本题思路较为简单,某个整数n的最少硬币兑换方式可以看成整数n-1的最少硬币兑换方式+一枚面值为1的硬币。因此可以列出如下的转移方程
通常如果能用动态规划的题目,也可以用递归解决,其思路与动态规划完全相同,寻找最优子问题。 因为递归存在大量重复解的问题,可以使用备忘录进行优化,其效果等同于动态规划,但是由于递归需要重复调用函数,其效率比动态规划要低。
本题另一种可行的思路是利用回溯做法,对于整数n,从coins中选择一个面值coin的硬币,并添加到track列表中,此时整数变为n-coin,重复上述步骤,直到整数变为0,此时track的大小即为该方法需要的硬币总数。
2. Coding
(1) 基于动态规划
int coinChange(vector<int>& coins, int amount)
{
if (amount < 0)
return -1;
vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= amount; ++i)
{
for (int coin : coins) //根据转移方程转移
{
if (i - coin < 0) continue; //n<0 dp[n]=-1 不处理
if (dp[i - coin] == -1) continue; //dp[n]=-1 不处理
int submc = dp[i - coin] + 1; //n>0
dp[i] = min(dp[i], submc);
}
dp[i] = dp[i] < INT_MAX ? dp[i] : -1;
}
return dp[amount];
}
(2)基于递归
int coinChange(vector<int>& coins, int amount)
{
if (amount == 0)
return 0;
if (amount < 0)
return -1;
int minc = INT_MAX;
for (int coin : coins)
{
int sub_minc = coinChange(coins, amount - coin);
if (sub_minc == -1) continue;
minc = min(minc, sub_minc + 1);
}
return minc < INT_MAX ? minc : -1;
}
(3)基于备忘录的递归
unordered_map<int, int> memocoin;
int coinChange(vector<int>& coins, int amount)
{
if (amount == 0)
return 0;
if (amount < 0)
return -1;
if (memocoin.count(amount) != 0)
return memocoin[amount];
int minc = INT_MAX;
for (int coin : coins)
{
int sub_minc = coinChange(coins, amount - coin);
if (sub_minc == -1) continue;
minc = min(minc, sub_minc + 1);
memocoin[amount] = minc;
}
return minc < INT_MAX ? minc : -1;
}
(4)回溯
int minn = LONG_MAX;
int coinChange(vector<int>& coins, int amount)
{
vector<int> track;
helpperCoinChange(coins, track, amount);
return minn < LONG_MAX ? minn : -1;
}
void helpperCoinChange(vector<int> sl, vector<int> track, int amount)
{
if (amount == 0)
{
minn = minn < track.size() ? minn : track.size();
return;
}
for (int i : sl)
{
if (i > amount)
continue;
track.push_back(i);
helpperCoinChange(sl, track, amount-i);
track.pop_back();
}
}