给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
思路
方法1:
dfs+剪枝
class Solution {
private:
int minsum;
public:
int coinChange(vector<int>& coins, int amount) {
if(coins.empty()) return -1;
sort(coins.begin(),coins.end());
if(amount==0) return 0;
if(amount<coins[0]) return -1;
minsum=INT_MAX;
int sum=0;
dfs(coins,amount,sum,coins.size()-1);
if(minsum==INT_MAX) return -1;
return minsum;
}
void dfs(vector<int>& coins, int amount,int sum,int s){
if(sum>=minsum||(sum+amount/coins[s]>=minsum)) return;//剪枝
if(amount==0){
minsum=min(minsum,sum);
return;
}
for(int i=s;i>=0;--i){
if(amount<coins[i]) continue;//剪枝
dfs(coins,amount-coins[i],sum+1,i);
}
}
};
方法二:
首先尝试贪心算法:
面值【1,2,5,7,10】,金额14,贪心则每次优先使用大面值的金额,如先选10,还剩4,在选两张2,得到最优数量3,但是金额14只需要两张7即可,所以贪心是不可行的
动态规划:
dp[i] 代表的并不是第几张钞票面值(与结果无关,不能递推),而是金额i的最优解(最小使用张数),这样才能通过递推dp[i]来得到最终结果
现在我们在已知dp[i-1],dp[i-2],dp[1]的情况下,要递推出dp[i]。
而金额(状态) i 可由:
金额 i-1 与面值1的组合
金额 i-2 与面值2的组合
金额 i-5 与面值5的组合
金额 i-7 与面值7的组合
金额 i-10 与面值10的组合
这样,每次状态的转移只是多加了一张,能够保证每次状态的转移是增加了最少的数量的。而能够得到最优解的条件就是找出最小的前一个状态的数量。即dp[i] = min(dp[i-1],dp[i-2],dp[i-5],dp[i-7],dp[i-10])+1
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int INF = amount + 1;
vector<int> dp(amount+1, INF);
dp[0] = 0;
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<amount+1;j++){
dp[j]=min(dp[j],dp[j-coins[i]]+1);//状态转移方程
}
}
return dp[amount]<INF?dp[amount]:-1;
}
};