LeetCode 518. 零钱兑换 II
题目描述


题意分析
给定若干种不同面额的硬币,每种硬币可以无限次使用,统计恰好凑出
amount的组合数量。组合只看每种硬币使用了多少枚,不区分拿取顺序;凑不出来时返回0。这里求的是方案数,不是最少硬币枚数。目标金额为
0时,不选任何硬币就是一份合法组合,因此答案为1。候选面额都是正数,统计时需要同时允许同一硬币重复使用,并避免把不同排列重复计入。
解法:完全背包组合计数
核心思路
[!blue]
先按硬币种类分阶段。处理某种硬币之前,
dp[j]表示只使用已经处理过的硬币、凑出金额j的组合数。初始还没有任何硬币,只有金额0能用空组合凑出,所以dp[0] = 1,其他位置为0。加入面额
coin后,金额j的组合可以分成互不重叠的两类:不使用当前硬币,保留原来的dp[j];至少使用一枚当前硬币,去掉其中一枚后,剩下的就是允许使用当前硬币、凑出j - coin的组合。因此将这两类相加,得到dp[j] += dp[j - coin]。为让右侧状态已经允许使用当前硬币,金额必须从小到大更新。因为
coin为正,j - coin < j,读到的较小金额已经是本轮状态,可以包含任意枚当前硬币;若倒序更新,读到的是上一阶段的状态,只能再加入一枚,便变成每种硬币最多使用一次。外层固定硬币种类,则规定了组合形成时的处理顺序。一个组合只会在处理其中最后一种硬币时按使用数量被统计,不会因为硬币加入先后不同而重复。若反过来先枚举金额、再枚举最后加入的硬币,就会把不同最后一步视为不同方案,统计成排列。
每轮开始金额为
coin,更小的金额不可能使用当前面额,保留原值即可。遍历所有硬币后,dp[amount]就覆盖了全部可用种类;全程只复用一维状态数组,不需要保存每个阶段的整张表。
解题步骤
- 创建长度为
amount + 1的数组,设置dp[0] = 1,其余为0。- 外层依次处理每种硬币
coin,不需要对面额排序。- 内层从
j = coin递增到amount,执行dp[j] += dp[j - coin]。- 全部种类处理结束后,返回
dp[amount]。
代码实现
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
// 先固定硬币种类,避免把不同选取顺序当成不同组合。
for (int coin : coins) {
// 金额正序让当前硬币可以继续重复使用。
for (int j = coin; j <= amount; j++) {
dp[j] += dp[j - coin];
}
}
return dp[amount];
}
}
func change(amount int, coins []int) int {
dp := make([]int, amount+1)
dp[0] = 1
// 先固定硬币种类,避免把不同选取顺序当成不同组合。
for _, coin := range coins {
// 金额正序让当前硬币可以继续重复使用。
for j := coin; j <= amount; j++ {
dp[j] += dp[j-coin]
}
}
return dp[amount]
}
复杂度分析
- 时间复杂度:$O(c(A + 1))$,
c为硬币种类数,A为目标金额。- 空间复杂度:$O(A + 1)$,保存金额零到
A的状态,零金额也有一份空组合。
关键点总结
[!green]
- 外层枚举硬币,保证统计的是组合而不是排列。
- 金额正序更新,保证当前硬币可以重复使用。
dp[0] = 1表示空方案,不能初始化为 0。- 完整二维状态是“前
k种硬币凑出金额j”,一维写法只是把k这一维滚动压缩。
易错点总结
[!yellow]
- 交换内外循环:先枚举金额再枚举最后一枚硬币,会把不同拿取顺序计成不同方案。
- 金额倒序更新:右侧读到上一阶段的状态,会限制当前硬币只能使用一次。
- 遗漏
dp[0] = 1:没有空组合这个起点,其他金额无法产生任何方案。- 把
+=写成赋值:会覆盖不使用当前硬币的已有方案,应将两类互不重复的组合相加。- 用最小值转移:本题累计组合数,应做加法;最小值转移解决的是最少硬币枚数。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 322. 零钱兑换 | 中等 | 候选硬币都可重复使用,原题求最少枚数,本题统计不区分顺序的组合数。 |
| 377. 组合总和 Ⅳ | 中等 | 两题都计数,但原题不同排列顺序算不同方案,循环顺序不能混用。 |
| 279. 完全平方数 | 中等 | 按金额或目标长度累积可重复选择的结果;本题先枚举硬币以统计无序组合,该题候选值为完全平方数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!