当前:LC518 · 零钱兑换 II · 首次出现于 Day 37 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC518 · Coin Change II · 动态规划

零钱兑换 II:组合数要把硬币放外层

问有多少种凑法,顺序不同算同一种。外层按硬币、内层金额正序 dp[j]+=dp[j−c],每种组合只按硬币出现顺序记一次。

dp[0]=1。对每枚硬币 c,正序扫 j=c..amount:dp[j]+=dp[j−c]。硬币在外层,组合按固定硬币顺序生成,1+2 与 2+1 不会重复。答案 dp[amount]。时间 O(A·C),空间 O(A)。

时间 O(A·C)空间 O(A)结论先行 · 全文约 6 节
导读

这是 LeetCode 518. Coin Change II。给你不同面额的硬币 coins 和总金额 amount,每种硬币无限枚,问凑出 amount 有多少种组合。顺序不同算同一种:1+2 和 2+1 是一种,不是两种。

主例 coins = [1, 2, 5],amount = 5。四种凑法:五个 1;一个 2 加三个 1;两个 2 加一个 1;一个 5。答案是 4。

LC322 问最少用几枚,那是另一张表。这里若把金额放外层、硬币放内层,会把 1+2 和 2+1 都加上,得到排列数。缺的是「先定硬币种类、再累金额」这条顺序。下面从这块缺口推出外层硬币的完全背包。

外层硬币,才是组合

输入给的是面额列表和目标金额。每种面额可以用 0 次、1 次、多次。方案是多重集合,不是序列。所以状态不该记住「最后一枚是谁的顺序」,只该记住「用到现在这些面额,凑出 j 有几种」。

第一直觉是对每个金额 j 枚举最后一枚硬币。dp[j] 加上 dp[j−c],每个 j 都能用到全部面额。这样 5 可以先 1 再 2 再 2,也可以先 2 再 1 再 2,路径不同就被加了多次。缺的是一个强制顺序:先考虑完硬币 1 的所有用法,再引入硬币 2,再引入 5。同一种集合只会在这个固定顺序下出现一次。

从缺口反推。一维数组 dp[j] 表示「只用到目前已引入的硬币,凑出 j 的组合数」。dp[0]=1,什么都不选是一种。引入硬币 c 时,j 从 c 正序走到 amount:dp[j]+=dp[j−c]。正序才能在同一轮里用到刚刚更新过的 dp[j−c],也就是同一枚硬币用多次——完全背包。倒序会变成 0-1,每种面额最多一枚,主例会丢掉 2+2+1。

用手走主例。先引入 1。j=1..5 每次加 dp[j−1],全变成 1:每个金额只有「全用 1」一种。演示第一帧 dp=[1,1,1,1,1,1],cur 指向硬币 1。再引入 2。j=2:1+dp[0]=2;j=3:1+dp[1]=2;j=4:1+dp[2]=3;j=5:1+dp[3]=3。表变成 [1,1,2,2,3,3]。dp[5]=3 对应 1×5、2+1×3、2+2+1,还没有 5。最后引入 5。只有 j=5:3+dp[0]=4。演示第三帧 dp[5]=4,四种凑法齐了。

金额放外层会得到 LC377 那种排列数,主例会大于 4,不要背「双重循环」却记反层次。amount=0 时只有空组合,返回 1。没有硬币且 amount>0,dp 停在 0。每个 (硬币, 金额) 对更新一次,时间 O(A·C),只要一维数组。

循环层次就是组合与排列硬币在外:每种集合按硬币顺序生成一次,是组合。金额在外:同一集合可按不同最后一枚到达,是排列。主例必须是前者,答案才是 4。
coins=[1,2,5], amount=5
硬币125
011121314151
只用 1 分:每种金额 1 种

Go:完全背包组合数

solution.goGo
func change(amount int, coins []int) int {
dp := make([]int, amount+1)
dp[0] = 1
for _, c := range coins {
for j := c; j <= amount; j++ {
dp[j] += dp[j-c]
}
}
return dp[amount]
}

1dp[0]=1 是空组合。amount 为 0 时直接返回这个 1。

2外层 range coins:先用完一种面额,再引入下一种,组合不会按不同顺序重复。

3内层 j 从 c 正序走:同一枚 c 可以连用,这是完全背包。倒序会错成 0-1。

4主例三轮之后 dp[5]=4,就是那四种凑法。

总结

组合数:硬币在外、金额正序累加。主例引入 1、2、5 之后 dp[5]=4。

  • LC322 问最少枚数;本题问方案数。把循环对调会变成排列,主例不再是 4。
  • 正序才能复用当前硬币。2+2+1 依赖同一轮里更新过的 dp[3]。
  • dp[0]=1 不能省。没有它,加 dp[j−c] 全是 0。
同族题目
LC322零钱兑换LC377组合总和 IVLC39组合总和