问题描述
给定不同面额的硬币
c
o
i
n
s
coins
coins 和一个总金额
a
m
o
u
n
t
amount
amount。编写一个函数来计算可以凑成总金额所需的最小的硬币个数、如果没有任何一种硬币组合能组成总金额,返回
−
1
-1
−1。
输入:
c
o
i
n
s
=
[
1
,
2
,
5
]
,
a
m
o
u
n
t
=
11
coins=[1, 2, 5], amount=11
coins=[1,2,5],amount=11
输出:
3
3
3
解释:
11
=
5
+
5
+
1
11=5+5+1
11=5+5+1
每种硬币的数量是无限的
解题报告
动态规划
d p [ i ] dp[i] dp[i] 表示组合成面额为 i i i 的硬币需要的硬币个数,则:
d p [ i ] = m i n ( d p [ i ] , d p [ i − c o i n [ j ] ] + 1 ) j 取值 [ 0 , 1 , ⋯ , n ] dp[i]=min(dp[i], dp[i-coin[j]]+1) \;\;\;j 取值[0, 1, \cdots, n] dp[i]=min(dp[i],dp[i−coin[j]]+1)j取值[0,1,⋯,n]
贪心+回溯
这种实现方式源自题解区:lkaruga
。
贪心思想:
为了使得总硬币数目最少,优先使用大面值的硬币,所以对
c
o
i
n
s
coins
coins 进行逆序排序;
回溯思想:
如果首先将大面值的硬币使用了,则可能无法凑出目标值,所以需要进行回溯,依次减少大面值的硬币数量
注意:最先找到不一定是最优解,所以其他情况我们也需要考虑。考虑其他情况时,合理使用剪枝,可以减少时间消耗。
【Update 2023/07/21】理解最先找到的不一定是最优解:如coins = {17, 7, 1},amount为23,如果首先选了17 则剩下只能选择6个1,结果为{17, 1, 1, 1, 1, 1, 1},共7个硬币,如果首先选择了7,则为{7, 7, 7, 1, 1},共5个硬币,很明显如果求出一个解就退出,则会错失最优解。
实现代码
动态规划实现
int coinChange(vector<int>&coins,int amount){
vector<int>dp(amount+1,amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.size();j++){
if(i>=coins[j]){
dp[i]=min(dp[i],dp[i-coins[j]]+1);
}
}
}
return (dp[amount]==amount+1)?-1:dp[amount];
}
};
贪心+回溯实现
class Solution{
public:
int coinChange(vector<int>&coins,int amount){
sort(coins.begin(), coins.end(),greater<int>());
int ans=INT_MAX;
dfs(coins, amount, 0, 0, ans);
return ans==INT_MAX?-1:ans;
}
void dfs(vector<int>&coins,int amount, int i, int count,int& ans){
if(amount==0){
ans=min(ans, count);
return;
}
if(i==coins.size()) return;
for(int k=amount/coins[i];k>=0&&k+count<ans;k--){
dfs(coins, amount-k*coins[i], i+1, count+k,ans);
}
}
};
参考资料
[1] Leetcode 322. 零钱兑换
[2] 题解区:lkaruga