一.题目:
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。
示例:
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
观察示例可以看到,目标和amount == 0时是返回0的,而去其他值如果不能被凑出来就是返回-1.
二.分析:
前面518. 零钱兑换 II和377. 组合总和 Ⅳ都是求组合数或者排列数,都需要累加,这题不需要。这题求凑成总金额所需的 最少的硬币个数
而且这题dp[j]表示取得目标值j所需的最少钱币个数,而不是钱币组合数
1.为什么不用分“取当前还是不取当前”两种情况
因为我们直接递推公式dp[j]的意义就是所需钱币的最少个数,这已经考虑到取还是不取的问题了,能不取当前遍历到的钱币的时候,肯定所需钱币会更少!除非必须取
2.为什么要min,而不是直接dp[j] = dp[j - coins[i]] + 1
每次取最小或者最少个数这种题,一般都要带上min()。
其实对于这道题来说,取min()和上面的说法一样,当我没有取当前钱币的时候,所需钱币不就最少吗,但是我不知道要不要取当前钱币i啊!
举个例子:
coins = [1, 2, 5],target = 5;
首先dp[0] = 0;目标为0肯定不用取任意一个钱币。
dp[1] = dp[1 - coins[0]] + 1,因为coins[1]及往后就比目标1大了,所以dp[1] = dp[1 - 1] + 1= dp[0] + 1 = 1
dp[2] = dp[2 - coins[0]] + 1 ,或dp[2 - coins[1]] + 1 ,coins[2]就不行了,5比目标2大。计算下来,还得是取coins[1]的时候dp[2] = dp[2 -2 ] + 1 = 1
,最小
所以,这个min就是在取不同i时,取那个能使得dp[j]最小的情况,可能当前i不取,只取了i - 1之前的
综合1和2,所以递推关系式从选择当前物品i出发,再用取一个min来表示取和不取两种情况。
dp[j] = min(dp[j], dp[j - coins[i]] + 1)
3.遍历循环顺序
因为本题既不是组合又不是排列,所以物品和背包谁在外面里面都无所谓。
此外,完全背包都是从小到大遍历。
三.代码
还有一些细节见代码:
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//取最小值问题,一般为了防止被覆盖,初值设置为最大整数
vector<int>dp(amount + 1, INT_MAX);
dp[0] = 0;//递推初始条件
for(int i = 0; i < coins.size(); ++i){
for(int j = coins[i]; j <= amount; ++j){
if(dp[j - coins[i]] < INT_MAX){ //因为dp[j - coins[i]]可能没有,即也为初值,那后面+1就会溢出
dp[j] = min(dp[j], dp[j - coins[i]] + 1);
}
}
}
//如果没有任何一种硬币组合能组成金额,那说明保留的是初始值,没变过,返回-1
if(dp[amount] == INT_MAX) return -1;
return dp[amount];
}
};