雪糕的最大数量 图解题解
这道题到底在问什么
- 输入
- costs=[1,3,2,4,1], coins=7
- 输出
- 4
- 输入
- costs=[10,6,8,7,7,8], coins=5
- 输出
- 0
- 输入
- costs=[1,6,3,1,2,5], coins=20
- 输出
- 6
最优解:为什么这么做
一句话答案:LeetCode 1833 雪糕的最大数量:固定现金要买最多支,把价格从小到大排序、从最便宜的一支支买、买不起就停,先便宜换回的支数最多,时间 O(n log n)。
固定现金,买最多支雪糕,返回什么
给一个价格数组 costs,costs[i] 是第 i 支雪糕的价格,再给一笔现金 coins。可以按任意顺序挑任意几支,只要总价不超过 coins,目标是买到的支数最多,返回这个最大支数。题面 costs=[1,3,2,4,1]、coins=7,答案是 4;换成 costs=[10,6,8,7,7,8]、coins=5,最便宜的一支都要 6 元,一支也买不起,答案是 0。
手里就这么点钱,先挑哪几支
现金是死的,雪糕价格参差不齐,能塞进预算的组合多到数不清。要是随手先抱一支贵的走,比如那支 4 元的,一下就吃掉大半现金,剩的钱只够再添一两支便宜货,同一笔钱花在贵雪糕上换回的支数就少。把所有子集都枚举一遍去比最多支数,n 一大就爆,得找个不试遍所有组合的挑法。
为什么先买最便宜的一批,支数一定最多
目标是支数最多,不是把钱押在哪一支上,那每一块钱就该尽量多换雪糕。把价格从小到大排好,从最便宜的一支起往贵里买,支数就顶到头了。可以这样说服自己:假设某种买法买到了最多的 k 支,里头却有一支比某支它没买的还贵,把这支贵的退掉、换成那支更便宜的没买雪糕,花的钱只减不增,支数仍是 k,方案照样合法。这种替换一路做下去,最省钱的 k 支永远落在全场最便宜的那 k 支上——从便宜到贵地买,绝不会比别的买法买得少。
排序之后,一支支怎么买、何时收手
排完序从最便宜那支起,每支只问一句:手里的现金够不够付它。只要现金大于等于当前价格 c,就买下它,现金扣掉 c、已买支数加一,接着看下一支更贵的。等碰到第一支付不起的——现金比它的价格还少——就当场停手,此刻已买的支数就是答案。为什么不再往后翻翻能不能捡个便宜?价格已经从小到大排好,这支是没买的里最便宜的一支,连它都付不起,后面更贵的一定也付不起。
[1,3,2,4,1] 排完序,7 元买到第几支
先把 [1,3,2,4,1] 排成 [1,1,2,3,4],现金 7 元、已买 0 支。第一支 1 元,7≥1 买下,剩 6 元、1 支;第二支 1 元,6≥1 买下,剩 5 元、2 支;第三支 2 元,5≥2 买下,剩 3 元、3 支;第四支 3 元,3≥3 正好付得起、花光现金买下,现金归零、4 支。轮到第五支 4 元,手里一分不剩付不起,停手。买到的正是最便宜的前四支、恰好花掉 7 元,得到 4 支。
几个写错就少买的地方,和它到底多快
不排序直接贪、或从贵的买起,会白白挤掉本可以拿的便宜支,支数算少。判定得用现金大于等于价格,等号要取到——现金恰好等于价格时花光也能拿下这一支,写成严格大于就会在钱数刚够时误判成买不起,少算一支。遇到第一支买不起别再往后翻找,排序后更贵的不可能反而买得起。
总时间是 O(n log n),n 是雪糕支数——最重的就是排序这一步,压过后面那趟从便宜到贵、每支一次比较一次扣钱的线性扫描。价格有明确上限时改用计数排序,能把排序压到线性、总体降到 O(n+max),买雪糕的逻辑一个字不用改。空间上只用现金和计数两个变量,额外开销都来自排序本身。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记牢一句:先把价格从小到大排序,从最便宜的一支支买,买得起(coins ≥ c)就买下 count 加一,轮到买不起的那支就停,count 即答案。下面每帧都在套这句。
- 4先看清画面。上面是原始价格 [1,3,2,4,1],第 i 格就是第 i 支雪糕的价格,顺序是乱的。右边这块购买状态记着我们最关心的三个量:剩余现金此刻是 7 元,已买 0 支,一分钱还没花。要买到最多支数,第一件事是把价格排好序,让便宜的排在前面。
- 5把价格从小到大排成 [1,1,2,3,4]。为什么要从便宜的开始?因为目标是支数最多,不是省钱本身,而是让每一块钱换回尽量多的雪糕。同样一笔钱,先买便宜的能换到更多支。要是先花大价钱抢那支 4 元的,后面本可以买好几支便宜的机会就被挤掉了。排好序,现金还是 7 元,准备从最左边这支 1 元开始一支支买。
- 6正式开买之前把起点钉牢。现在一支雪糕都还没买,手里 7 元原封不动,已买支数 count 等于 0。接下来从最便宜的那支起,一支支往贵的方向走,买得起就买、现金减价格、count 加一,直到遇上一支买不起的为止。目标是尽量多买几支。
- 7盯住最左边这支待考察的雪糕,记住贯穿全程的那把尺子。每一步只问一句:手里剩的现金够不够付它的价格?只要剩余现金大于等于当前价格 c,就买得起,买下它,现金扣掉 c、count 加一。一旦剩的钱比当前这支还少,连排在最前的最便宜一支都付不起,那后面更贵的更别想,直接停。下面就拿这把尺子从 1 元那支一支支量过去。
- 8轮到最便宜的第 1 支,价格是 1 元。此刻还没买任何雪糕,手里 7 元。先把它拎出来,看看手里这 7 元付不付得起这 1 元。
- 9拿尺子一量:手里 7 元,大于等于这支的 1 元,买得起。既然付得起,就没有理由不买,毕竟每多买一支都是赚。把它买下来。
- 10把这支 1 元雪糕正式买下(标绿)。现金从 7 元付掉 1 元,剩 6 元;已买支数从 0 加到 1 支。接着看下一支更贵一点的。
- 11轮到第 2 支,价格是 1 元。前面 1 支已经买下(蓝色),现金花到只剩 6 元,已买 1 支。先把它拎出来,看看手里这 6 元付不付得起这 1 元。
- 12拿尺子一量:手里 6 元,大于等于这支的 1 元,买得起。既然付得起,就没有理由不买,毕竟每多买一支都是赚。把它买下来。
- 13把这支 1 元雪糕正式买下(标绿)。现金从 6 元付掉 1 元,剩 5 元;已买支数从 1 加到 2 支。接着看下一支更贵一点的。
- 14轮到第 3 支,价格是 2 元。前面 2 支已经买下(蓝色),现金花到只剩 5 元,已买 2 支。先把它拎出来,看看手里这 5 元付不付得起这 2 元。
- 15拿尺子一量:手里 5 元,大于等于这支的 2 元,买得起。既然付得起,就没有理由不买,毕竟每多买一支都是赚。把它买下来。
- 16把这支 2 元雪糕正式买下(标绿)。现金从 5 元付掉 2 元,剩 3 元;已买支数从 2 加到 3 支。接着看下一支更贵一点的。
- 17轮到第 4 支,价格是 3 元。前面 3 支已经买下(蓝色),现金花到只剩 3 元,已买 3 支。先把它拎出来,看看手里这 3 元付不付得起这 3 元。
- 18拿尺子一量:手里 3 元,大于等于这支的 3 元,买得起。注意这里现金和价格正好相等,3 元付 3 元刚好花光,这一支照样买得起、要算进去,这正是判定用大于等于、把等号取到的地方。把它买下来。
- 19把这支 3 元雪糕正式买下(标绿)。现金从 3 元付掉 3 元,正好全部花光;已买支数从 3 加到 4 支。接着看下一支更贵一点的。
- 20轮到第 5 支,价格是 4 元。前面 4 支已经买下(蓝色),现金已经花光,已买 4 支。先把它拎出来,看看手里已经一分不剩,还付不付得起这 4 元。
- 21拿尺子一量:手里的现金已经一分不剩,却要付 4 元,钱不够,这支买不起(标红)。前面 4 支便宜的已经把现金花得差不多了,轮到这支就付不起了。
- 22为什么这支买不起就能直接停,不用再看后面?因为价格已经从小到大排好,这支 4 元正是剩下里最便宜、也是唯一没买的一支(已标红)。连它都付不起,后面就算还有更贵的雪糕也一定买不起。所以到此为止,能买到的最大支数就锁定在 4 支。
- 23回头验证一下这个答案。买下最便宜的前 4 支,价格是 1、1、2、3,一共 7 元,正好等于手里的 7 元,现金正好全部花光。剩下那支 4 元的(标红)确实买不起。想再多买一支是不可能的,因为便宜的都已经买光了,答案就是 4 支。
- 24回看整条路:先把价格排成 [1,1,2,3,4],从最便宜的一支支买。1 元买下、又一个 1 元买下、2 元买下、3 元买下,现金正好花光,买到 4 支;轮到 4 元那支时钱不够,停手。整道题的窍门就一句:先排序,再从便宜到贵地买,买得起就买、买不起就停。最终答案 4。
⚠️ 容易写错的地方
✗ 错:不排序就贪心,或者从贵的雪糕开始买,导致买到的支数偏少
✓ 对:一定先把价格从小到大排序,再从最便宜的一支支买
目标是支数最多,不是把钱花在哪支上。同一笔钱,先买便宜的能换回更多支。若先花大价钱买贵的,就挤掉了本可以买好几支便宜雪糕的机会,支数会算少。排序让便宜的排在前面,是这套贪心成立的前提
✗ 错:把判定写成「现金大于价格才买」,漏掉现金正好等于价格的情况
✓ 对:判定是「现金大于等于价格就买」,coins ≥ c、等号要取到
当手里现金正好等于这支的价格时,花光刚好能买下它,这一支应该算进去。参考代码写成「现金比价格还小才停」,正好包含了现金等于价格时继续买。若写成严格大于才买,就会在钱数恰好够时误判成买不起,支数少算一支
✗ 错:遇到一支买不起,还接着去看后面的雪糕能不能捡漏
✓ 对:排序后遇到第一支买不起的,直接停手返回
价格已经从小到大排好,当前这支是剩下里最便宜的。连它都买不起,后面只会更贵,一定也买不起,继续看纯属浪费。一旦买不起就能立刻收尾,这也是排序带来的好处
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def maxIceCream(self, costs: List[int], coins: int) -> int:
costs.sort()
for i, c in enumerate(costs):
if coins < c:
return i
coins -= c
return len(costs)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int maxIceCream(vector<int>& costs, int coins) {
sort(costs.begin(), costs.end());
int n = costs.size();
for (int i = 0; i < n; ++i) {
if (coins < costs[i]) return i;
coins -= costs[i];
}
return n;
}
};Java
import java.util.*;
class Solution {
public int maxIceCream(int[] costs, int coins) {
Arrays.sort(costs);
int n = costs.length;
for (int i = 0; i < n; ++i) {
if (coins < costs[i]) {
return i;
}
coins -= costs[i];
}
return n;
}
}复杂度
时间
O(n log n)
n 是雪糕支数。主要开销在排序 O(n log n);排完之后只从便宜到贵扫一遍,每支做一次比较加一次现金累减,是 O(n)。两者相加由排序主导,总体 O(n log n)。若按题目提示改用计数排序,排序降到 O(n + max),总体可优化到线性,max 是最大价格
空间
O(log n) / O(n)
按峰值算,且只算排序带来的额外开销。算法本身只用现金与计数两个变量,是常数 O(1)。排序要占额外空间:C++ 的 sort、Java 的 Arrays.sort 用递归快排,栈深 O(log n);Python 的 sort 用 Timsort,最坏 O(n)。所以峰值 C++ 与 Java 记 O(log n),Python 记 O(n);若改计数排序则是 O(max)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 雪糕的最大数量 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
先买最便宜的,会不会只是这几个例子凑巧,换组数据就不成立?+
不会,可以推。假设最优买法买到了 k 支,但没优先挑最便宜的,那它买的这 k 支里必然有一支,比某支它没买的雪糕还贵。把这支贵的换成那支更便宜的没买雪糕,总花费只减不增,支数仍是 k,方案依旧合法。反复替换,最终买的正好是全场最便宜的 k 支,花得还更少。这说明按从便宜到贵的顺序买,得到的支数不会输给任何别的买法,所以它就是最大支数。
题目提示可以用计数排序,和普通排序比省在哪?+
普通排序是 O(n log n),对这题够用。但价格 costs[i] 有明确上限,比如不超过十万,这种取值范围有限的整数正适合计数排序:开一个大小为最大价格加一的桶,数清每种价格各出现几次,再从价格 0 往上依次取出,就排好了序,时间 O(n+max),max 是最大价格。当 n 很大、价格范围相对小时,它比 O(n log n) 更快,能把整体压到线性。买雪糕那段逻辑原封不动,只是把排序换个更快的实现。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 雪糕的最大数量 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。