322. 零钱兑换(中等)
题目
给你若干种面额的硬币(每种数量无限)和一个目标金额 amount,请用最少的硬币枚数恰好凑出该金额;如果任何组合都凑不出来,返回 -1。
换句话说:这是一道不限使用次数的完全背包,只不过优化目标从“能不能凑 / 有多少种方案”换成了“最少用几枚”。
自造示例:coins = [1,3,4], amount = 6 → 2(3 + 3,比 4 + 1 + 1 更少)。
再如 coins = [2,5], amount = 11 → 4(5 + 2 + 2 + 2);coins = [2], amount = 3 → -1。
约束:硬币种类数 1 ~ 12;单枚面额 1 ~ 231-1;目标金额 0 ~ 104;amount = 0 时答案固定为 0。
思路
一句话:把“凑 amount 最少几枚”拆成“最后一次选哪枚硬币”,用 dp[a] 记录凑金额 a 的最少枚数,从小到大推。
先想最直白的贪心:每次都尽量用最大面额。它对 [1,2,5], 11 碰巧成立(5+5+1),但换成 [1,3,4], 6 就翻车:贪心先拿 4,剩下 2 要两个 1,共 3 枚;最优其实是 3+3,只要 2 枚。
再想枚举所有组合:每种硬币可不限次地选,搜索树分支极多,而且“凑 3 需要几枚”这类子问题会被反复重算,是指数级时间,不可接受。
关键观察:这个问题有最优子结构。如果最后一次选的硬币面额是 c,那么凑 amount 的最少枚数就是 1 + 凑(amount - c) 的最少枚数;对所有可选 c 取最小即可。于是得到转移:
dp[a] = min(1 + dp[a - c]),其中 c 遍历所有 c ≤ a 的硬币。
边界 dp[0] = 0(凑 0 元不需要硬币),其余先设成“无穷大”表示暂时凑不出。每个子状态只算一次、每个状态尝试 n 种硬币,复杂度 O(n·amount),完全可接受。
解法
用一维数组 dp[0..amount]:dp[a] 表示凑出 a 的最少硬币数。初始 dp[0] = 0,其余为 amount + 1(当作无穷大哨兵),然后按金额从小到大填充。
- 令
dp[0] = 0,其余全部为amount + 1。 - 对每个金额
a = 1..amount:枚举每种硬币c,若c ≤ a,用1 + dp[a-c]更新dp[a]。 - 最终
dp[amount]仍是哨兵就返回-1,否则返回dp[amount]。

| a | 可选 c | dp[a] | 说明(coins = [1,3,4]) |
|---|---|---|---|
| 0 | — | 0 | 空集 |
| 1 | 1 | 1 | 1 + dp[0] |
| 2 | 1 | 2 | 1 + dp[1] |
| 3 | 1, 3 | 1 | min(1+dp[2]=3, 1+dp[0]=1) |
| 4 | 1, 3, 4 | 1 | min(1+dp[3], 1+dp[1], 1+dp[0]) |
| 5 | 1, 3, 4 | 2 | min(1+dp[4], 1本篇为付费内容您已阅读试读部分,购买后即可解锁全文。 ⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。 |
还没有评论,来说两句?