322. 零钱兑换(Coin Change)¶
频次 ★★★★★ · 难度 🟡 · 高频:字节/美团/阿里
题目¶
不同面额硬币 coins(无限供应),凑目标金额 amount 所需的最少硬币数,无法凑出返回 -1。
示例:
思路¶
完全背包问题(DP):dp[i] 表示凑 i 元的最少硬币数。
dp[i] = min(dp[i], dp[i - coin] + 1) for each coin ≤ i
初始化 dp[0] = 0,其余为 INF(amount + 1 即可)。
代码¶
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1); // 上界(不可能达到)
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
dp[0] 设 0 还是设 1——按语义读,不按题背(本章第五考)¶
dp[0] = "目标 quantity=0 时的真实答案"——直接读出来即可。
| 题 | dp 语义 | dp[0] | 理由 |
|---|---|---|---|
| 494/518/377 | 方案/组合/排列数 | 1 | 空方案也要被数到 |
| 416 | 判定 | true | 不选任何即凑出 0 |
| 322(本题) | 最少枚数 | 0 | 0 元用 0 枚——枚数为 0 |
- 计数流:dp[0]=1、
+=、凑不出=0;最值流:dp[0]=0、min、凑不出=伪∞ → -1 - 本题实际踩点:
dp[0]=1(计数语义串门)→ 全表 +1(min 链底端污染)、amount=0 返 -1——语义倒推一行修正。 - 伪 ∞ 用
amount+1万不可Integer.MAX_VALUE(+1 溢出);贪心不成立([1,3,4] 凑 6 → 贪 4+1+1=3 vs 真 3+3=2)
复杂度¶
- 时间:O(amount × n) —— n 为硬币种类数
- 空间:O(amount)
边界条件¶
- amount = 0:返回 0
- 无解(如 [2], amount = 3):dp[3] 保持 INF → 返回 -1
- 大面额硬币超过 amount:不影响
变式¶
- 518. 零钱兑换 II:求凑法种数(完全背包计数),
dp[i] += dp[i-coin] - 70. 爬楼梯:每步可走 coin 种步数,问方案数
易错点¶
- INF 初始化:
amount + 1是因为最多 amount 枚硬币(全部用 1 元),所以 amount+1 肯定不可达 - 内层循环顺序:零钱兑换最少硬币数(完全背包求最值)对顺序不敏感,外面 for coin 或外面 for amount 都行。但如果是求组合数(518),外面 for coin 防重复计数
- 返回前检查
dp[amount] > amount
面试追问¶
- 为什么 518 换顺序就会重复计数? 外层 for coin 保证"先选定面额再遍历金额",每种组合只被计数一次(coin 的选取有顺序)。最少硬币数是最值,无所谓顺序
关联题¶
- 同套路:518. 零钱兑换 II —— 计数版
- 进阶:300. 最长递增子序列 —— 一维 DP 另一模式
- 知识点:完全背包问题(求最值 vs 计数)见动态规划