
图中每种面额单独形成一层,状态转移只能使用不超过库存的数量。
面额大的硬币先拿,剩下金额再继续,这个办法什么时候正确?先把题意说完整:本文讨论任意正整数面额、每种数量有限、恰好凑出目标金额,并最小化硬币总数。若题目额外给定特殊面额结构,可以另行证明更简单策略,不能把一般情况与特例混用。
一个贪心走入死路的例子
自设面额为一、三、四,库存分别是一枚、两枚、一枚,目标为六。先拿四,再拿一,剩下一却已经没有一元硬币,贪心无法完成。但两枚三可以刚好组成六,只需要两枚。
所以“贪心没凑出来”不能直接解释为问题无解。如果把一元库存改成两枚,贪心能用四加一加一凑到六,却仍然比三加三多一枚。可行性和最优性,是两种分别需要证明的结论。
这个反例针对这里明确的任意面额版本,不用于否定某个特定整除面额体系中的已证明算法。面试中先确认面额、库存与目标条件,再决定是否能用贪心,不能看到“硬币”两个字就默认一种套路。
让状态包含已处理的种类
定义 F_i(t) 为只使用前 i 种硬币,恰好组成金额 t 所需的最少枚数。初始时,没有硬币只能组成零,所以 F_0(0)=0,其他金额都不可达。不可达必须使用独立标记,不能和需要零枚混淆。
处理第 i 种面额 v、库存 c 时,枚举使用 k 枚,其中 k 不超过库存,也不能让金额 t-kv 为负。若上一层能够组成 t-kv,就得到候选 F_{i-1}(t-kv)+k,取所有候选最小值。
这条转移把每个可行方案按“最后这一种用了多少枚”分类,既覆盖所有库存内选择,又不会凭空使用超量硬币。最优性来自上一层已经对相应剩余金额取过最少枚数,再比较当前层的全部合法数量。

为什么强调从上一层读取
最直接的实现使用两个数组,旧数组保存前 i-1 种的答案,新数组保存加入当前种类后的结果。枚举 k 时始终读旧数组,避免刚更新的状态再次使用同一种硬币,意外把有限库存变成无限供给。
直接套完全背包的正向金额循环,会允许同种硬币反复进入状态,不符合这里的库存约束。另一方面,简单把金额改成倒序,也不自动处理“每种可以拿多枚但有上限”的全部情况,需要相应的拆分或计数设计。
面对一个没把库存信息写进转移的程序,先问它怎样阻止第三枚三元硬币被使用,比只检查遍历方向更有效。状态表达不完整,循环顺序再熟练也救不了题意。
怎样核对无解与边界
目标为零时,空选择就是合法方案,答案为零。目标为正但所有库存都为零,则不可达。面额为零或负数不属于当前模型,应拒绝这类输入,而不是让枚举陷入无意义的重复。
同一面额若在输入中重复出现,可以按接口约定合并库存,再进行转移。库存大于目标所需上限时,枚举数量可以截到目标除以面额,减少无用计算,但不能修改目标必须恰好组成的要求。
结果不可达时,返回专门的无解标记,而不是把很大的初始值当成真实硬币数。若还要求输出方案,可以记录选用了多少枚以及上一金额,再从目标状态回溯核对库存。
复杂度取决于金额和库存
朴素分层方法的工作量可以按目标金额乘各类有效枚举上限之和估计,空间用两层数组压到 O(A),A 为目标金额。它依赖金额数值,不是只依赖输入数字的位数,因此不适合直接处理任意巨大的目标。
可以进一步使用二进制拆分等有界背包优化,但优化前应先有一个小规模可靠版本。对短面额列表枚举所有库存选择,与动态规划对照,能有效抓住超量使用和无解判断的错误。
这道题的可迁移部分是“恰好组成、最少数量、有限库存”怎样进入状态。如果你的程序在只剩一枚三元硬币时,仍然给金额六返回两枚,你会从哪一层转移开始排查?