dp 数组含义定义一维数组 dp:
题目要求凑成 amount 的最少硬币数,所以最终答案就是:
如果某个金额无法被凑出来,就让它保持为一个很大的值,最后再判断是否需要返回 -1。
假设当前有一枚硬币 coin,现在要计算金额 j 的最少硬币数。
如果选择使用这枚硬币,那么在使用它之前,需要先凑出金额:
如果 j - coin 可以凑出来,那么再加上当前这 1 枚硬币,就可以凑出 j:
但是 dp[j] 可能之前已经通过其他硬币组合得到过更小值,所以要取最小值:
一句话总结:凑成金额 j 的最后一步,可以是先凑成 j - coin,再放入一枚面值为 coin 的硬币。
dp 数组如何初始化dp[0] = 0:
其他位置初始化为 Infinity:
原因是:一开始还不知道这些金额能不能被凑出来,用 Infinity 表示“暂时无法凑成”。
后续转移时,如果某个金额可以被凑出来,就会被更新成更小的硬币数量。
例如:
数组长度是 amount + 1,因为下标要从 0 一直到 amount,每个下标都表示一个具体金额。
这道题每种硬币可以使用无限次,所以是完全背包问题。
可以先遍历硬币,再正序遍历金额:
固定当前硬币 coin 后,内循环会依次计算:加入这枚硬币后,凑成金额 j 最少需要多少枚硬币。
j 表示当前要凑出的金额j 不是硬币数量,而是当前正在计算的目标金额。
例如 coin = 2、amount = 5 时,内循环中的 j 会依次取:
也就是依次判断:使用硬币 2 后,能否让 dp[2]、dp[3]、dp[4]、dp[5] 变得更小。
j = coin 开始当目标金额小于当前硬币面值时,无法放入这枚硬币。
例如当前硬币是 5:
因此不需要检查这些金额,直接从 j = coin 开始。这样还能保证 j - coin >= 0,访问 dp[j - coin] 时不会越界。
这行代码是在比较两种选择:
dp[j]。j - coin,再加上一枚面值为 coin 的硬币,硬币数为 dp[j - coin] + 1。例如当前 coin = 2,正在计算 j = 5:
其中:
dp[5] 表示不使用当前硬币 2 时,之前已经得到的最优结果。dp[3] + 1 表示先凑成金额 3,再加入一枚硬币 2,从而凑成金额 5。如果 dp[3] 仍然是 Infinity,说明金额 3 无法凑出:
此时 dp[5] 不会被这个无效方案更新。
j 必须正序遍历内循环正序遍历时,较小金额的状态会先被更新,后面的较大金额可以继续使用这个新状态,因此同一种硬币可以使用多次。
假设开始时:
内循环的更新过程如下:
计算 dp[4] 时使用了当前硬币这一轮刚更新的 dp[2],所以硬币 2 被使用了两次。这就是完全背包中金额需要正序遍历的原因。
如果改成倒序遍历,计算 dp[4] 时看到的 dp[2] 还是上一轮的旧值,同一轮中当前硬币只能被使用一次,就变成了 0-1 背包的处理方式。
dp 数组以 coins = [1, 2, 5]、amount = 11 为例。
初始化:
| 金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp | 0 | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity | Infinity |
1硬币 1 可以凑出所有金额:
| 金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
例如:
表示凑成金额 3 最少需要 3 枚 1。
2加入硬币 2 后,很多金额可以用更少硬币凑出来:
| 金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 5 | 6 |
例如:
表示金额 4 可以用 2 + 2 凑出来,只需要 2 枚硬币。
5继续加入硬币 5:
| 金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp | 0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 |
例如:
此时 dp[6] = 2,表示金额 6 可以用 5 + 1 或 2 + 2 + 2 中更优的方式凑出来,再加一枚 5,金额 11 最少需要 3 枚硬币。
最终:
对应一种方案:
所以返回 3。
O(coins.length * amount),需要枚举每枚硬币和每个金额。O(amount),只需要一个一维 dp 数组。