518. 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins 中的所有值 互不相同
0 <= amount <= 5000
题目描述
给定一组货币面额和一个总金额,计算可以凑出该总金额的硬币组合数。如果没有任何一种组合可以凑出该金额,则返回 0。
算法分析
这个问题可以通过动态规划来解决。我们创建一个数组 dp
,其中 dp[j]
表示凑出金额 j
的组合数。我们遍历所有可能的硬币组合,并更新 dp
数组。
算法步骤
- 初始化一个数组
dp
,其大小为amount + 1
,所有元素初始化为 0,除了dp[0]
初始化为 1。 - 遍历所有可能的硬币组合。
- 对于每个硬币
coins[i]
,更新dp[j]
为dp[j] + dp[j - coins[i]]
,其中j
是当前考虑的金额。 - 最后,返回
dp[amount]
。
算法流程
具体代码
//dp[j]+=dp[j-coins[i]]
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1,0);
dp[0]=1;
for(int i=0;i<coins.size();i++)
{
for(int j=coins[i];j<=amount;j++)
{
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
};
算法分析
复杂度分析
- 时间复杂度:O(amount * coins.size()),其中
amount
是总金额,coins.size()
是硬币种类的数量。 - 空间复杂度:O(amount),我们需要一个数组来存储每个金额的组合数。
易错点
- 在初始化
dp
数组时,确保所有元素都被正确初始化。 - 在更新
dp
数组时,确保正确地计算组合数。 - 在返回结果时,确保正确地返回
dp[amount]
。
注意事项
- 确保在遍历硬币组合时不要超出数组的边界。
- 在处理数组时,确保不会覆盖任何元素。
相似题目
背包问题模板
首先是背包分类的模板:
1、0/1背包:外循环nums,内循环target,target倒序且target>=nums[i];
2、完全背包:外循环nums,内循环target,target正序且target>=nums[i];
3、组合背包(考虑顺序):外循环target,内循环nums,target正序且target>=nums[i];
4、分组背包:这个比较特殊,需要三重循环:外循环背包bags,内部两层循环根据题目的要求转化为1,2,3三种背包类型的模板
然后是问题分类的模板:
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合问题:dp[i]+=dp[i-num];