雪糕的最大数量 图解题解
这道题到底在问什么
- 输入
- 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
最优解:一步一步想明白
- 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) 更快,能把整体压到线性。贪心买的逻辑一个字都不用改,只是把排序换成更快的实现。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 雪糕的最大数量 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。