322. 零钱兑换(中等)

Blind 75 › 322. 零钱兑换(中等) (第 7 章)
文章工具

题目

给你若干种面额的硬币(每种数量无限)和一个目标金额 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(当作无穷大哨兵),然后按金额从小到大填充。

  1. 令 dp[0] = 0,其余全部为 amount + 1。
  2. 对每个金额 a = 1..amount:枚举每种硬币 c,若 c ≤ a,用 1 + dp[a-c] 更新 dp[a]。
  3. 最终 dp[amount] 仍是哨兵就返回 -1,否则返回 dp[amount]。
322 零钱兑换 算法流程图
算法流程图:自底向上填充 dp 数组
a可选 cdp[a]说明(coins = [1,3,4])
0—0空集
1111 + dp[0]
2121 + dp[1]
31, 31min(1+dp[2]=3, 1+dp[0]=1)
41, 3, 41min(1+dp[3], 1+dp[1], 1+dp[0])
51, 3, 42min(1+dp[4], 1

🔒

本篇为付费内容

您已阅读试读部分,购买后即可解锁全文。

⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。

还没有评论,来说两句?

发表评论

欢迎交流技术细节;广告与人身攻击会被删除。

在线咨询