中 · 贪心
最少硬币数
收银员不做规划:拿起装得下的最大硬币,一次又一次。这个策略 —— 每步都拿当前最优 —— 就是贪心,而且在这套硬币系统里,它可以被证明是最优的。
一句话答案先看 25 能装几个,再 10、再 5、最后 1:<code>amount / 25</code> 枚 25,金额换成 <code>amount % 25</code>,依此类推。四行,不用搜索。
题目
输入: 一个整数 amount(1 ≤ amount ≤ 100000),要支付的分数。
输出: 凑出这个金额所需的最少硬币数(面值 25、10、5、1)。
样例:
| 输入 | 输出 |
|---|---|
30 | 2 |
99 | 9 |
1 | 1 |
贪心不是自然法则 —— 它是这套面值的性质。题解之后你会遇到一套让同一策略翻车的硬币,这正是这道题背后藏着的面试题。
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。
卡住了?提示
提示 1 — 除法就是批量操作
amount / 25 就是一次能装下多少枚 25 —— 根本不需要对硬币写循环。拿完之后,剩下要付的是 amount % 25。对 10、对 5 重复;最后剩下多少,就得用多少枚 1。
提示 2 — 为什么「先拿最大的」在这里是最优的
最优解里,1 至多 4 枚(5 枚就换成 5),5 至多 1 枚(2 枚就换成 10),而 25 装得下时,10+10+5 的组合不比一枚 25 省(10+10+5 = 25)。按面值数到这些上限为止,剩下的全部换成 25 —— 这正是贪心的选择。证明就是这笔计数;贪心四行就把它算出来了。
提示 3 — 知道贪心什么时候会翻车
把同一策略用到面值 {4, 3, 1}、金额 6 上:贪心拿 4+1+1 = 3 枚,而 3+3 只要 2 枚。贪心只在 25/10/5/1 这类规范系统里保证最优。面试官问「这永远对吗?」,这个例子就是答案。
题解(先自己试试!)
#include <iostream>
using namespace std;
int main() {
int amount;
cin >> amount;
int coins = 0;
coins += amount / 25; amount %= 25;
coins += amount / 10; amount %= 10;
coins += amount / 5; amount %= 5;
coins += amount; // the rest is paid in 1s
cout << coins << endl;
return 0;
}
贪心在这里成立,是因为这套面值允许它。当它不成立时 —— 上面的 {4, 3, 1},或者真实货币的各种怪癖 —— 你就得搜索,标准升级路线是动态规划。在那之前,先见另一个「用结构换搜索」的想法:区间求和 花 O(n) 做一次准备,之后每个问题 O(1) 回答,和贪心做的是同一笔交易。
深度解析
为什么这样做对
拿起装得下的最大硬币,重复。对 25/10/5/1 这可以证明是最优的:最优解里 1 不超过 4 枚、5 不超过 1 枚、(在 25 装得下时)「两个 10 加一个 5」不优于一枚 25 —— 超过这些上限的面额必须是 25,这正是贪心先拿的东西。
代价
四次除法四次取余:时间和空间都是 O(1),与金额无关。一般版本(任意面值、问金额 X 能否支付)需要动态规划,O(amount × 硬币数)。
常见错法
- 以为贪心永远最优。面值 {4, 3, 1}、金额 6 时它付 4+1+1(三枚),而 3+3 只要两枚 —— 贪心只在规范系统里安全。
- 一枚一枚循环(
while (amount >= 25) { coins++; amount -= 25; })而不用除法。答案一样,但/和%本来就是这个循环的一步到位版。
下一步
「付一次、答多次」的交易在 区间求和 里继续 —— 准备一次,之后每个查询 O(1)。
记住这两句
- 贪心 = 每步拿当前最优;按面值系统证明,别假设。
- 面值 c 一次能装
amount / c枚;剩下的是amount % c。
🐞 这段是错的 —— 点开,亲手让它挂一次
把剩下的零头丢了 —— 1 分硬币从来没被数进去
#include <iostream>
using namespace std;
int main() {
int amount;
cin >> amount;
int coins = 0;
coins += amount / 25; amount %= 25;
coins += amount / 10; amount %= 10;
coins += amount / 5;
cout << coins << endl;
return 0;
}