爱吃香蕉的珂珂 图解题解
想找最小可行速度,不用一档一档试——速度和耗时的单调关系,让二分直接锁定答案。
就像评估快递员每小时最多派几单才能按时派完——你不用从「每小时1单」一路试到「每小时全部单量」,因为速度越快耗时只会越短,这种单调性是二分的信号。在速度区间 [1, max] 上取中间值,写个判定函数算「这个速度要几小时」:够快就往左找更慢的可行解,太慢就往右提速——每次砍一半,直到逼出最小速度。
这道题到底在问什么
- piles
- [3, 6, 7, 11]
- h
- 8
- 输出
- 4
先想最直接的笨办法
最直接:速度 = 1、2、3… 挨个试,第一个「能在 h 小时内吃完」的就是答案。最坏要试到 max(piles) 那么大,香蕉堆里有上亿根就试上亿次。但这里藏着规律——速度越大,耗时只会越短、绝不会忽长忽短,这种单调性正是二分的信号。(动画第 3 步)
最优解:一步一步想明白
- 3最直接:速度 = 1、2、3… 挨个试,第一个「能在 h 小时内吃完」的就是答案。最坏要试到 max(piles) 那么大,香蕉堆里有上亿根就试上亿次。但这里藏着规律——速度越大,耗时只会越短、绝不会忽长忽短,这种单调性正是二分的信号。
- 4转折:与其挨个试速度,不如在速度区间 [1, max(piles)] 上二分。先写个判定函数 hours(k):逐堆 ⌈piles[i]/k⌉ 求和,得到吃完要几小时。不变量:hours(k) 随 k 增大单调递减。对中间速度 mid,hours(mid) ≤ h 说明够快、还能更慢(收右);大于 h 说明太慢(收左)——照样每步砍一半。
- 5l=1, r=11把候选速度排成一排(1~11),左指针 l=速度1,右指针 r=速度11。注意:二分的对象是速度,不是香蕉堆;每个速度够不够快,靠判定函数 hours 算。
- 6mid 速度 = 6中间下标取速度 6。先别急着收缩,用判定函数算算速度 6 到底要几小时。
- 7hours(6)=6 ≤ 8速度 6:每堆向上取整算小时,1+1+2+2 = 6 小时 ≤ 8,够快!它是个可行解,但说不定还能更慢,所以保留它、往左找更小的。
- 8r = mid = 5(速度6)速度 7~11(更快但更浪费)整段灰掉丢弃,r 收到 mid(不减 1,速度6 自己可能就是答案)。范围缩到速度 [1, 6]。
- 9mid 速度 = 3新范围 [1, 6],中间取速度 3。继续用判定函数算它要几小时。
- 10hours(3)=10 大于 8速度 3:1+2+3+4 = 10 小时,大于 8,超时了,不可行。说明速度 3 以及比它更慢的都不行,得加速——这一半要丢掉。
- 11l = mid+1(速度4)速度 1、2、3(含 mid)都太慢,整段灰掉,l 跳到 mid+1(速度4)。范围缩到速度 [4, 6]。
- 12mid 速度 = 5,hours(5)=8范围 [4, 6],中间速度 5:1+2+2+3 = 8 小时,正好 ≤ 8,够快。保留它往左找,r 收到 mid(速度5)。范围缩到速度 [4, 5]。
- 13mid 速度 = 4,hours(4)=8范围 [4, 5],中间速度 4:也是 8 小时 ≤ 8,够快。r 收到 mid(速度4),此时 l == r,区间只剩速度 4,循环结束。
- 14最小速度 = 4l 和 r 撞在速度 4——这就是能在 8 小时内吃完的最小速度。再慢一档(速度3)就超时。
- 17「求最小/最大的、满足某条件的值」,且条件随值单调,就能二分答案:x 的平方根、D 天送货都是它。
⚠️ 容易写错的地方
✗ 错:在 piles 上二分、上界乱填
✓ 对:在速度区间 [1, max(piles)] 上二分
二分的对象是答案(速度),不是数组;上界必须取 max(piles),因为最快也只需每小时吃完最大那堆,再大没意义
✗ 错:hours(mid) <= h 时写 l = mid+1
✓ 对:此时 r = mid(往更小速度收)
我们要的是满足条件的最小值,够快时应保留 mid 并往左找,方向写反会得到偏大答案
完整代码(Python / C++ / Java)
Python
class Solution:
def minEatingSpeed(self, piles, h):
def hours(k):
return sum((p + k - 1) // k for p in piles) # 各堆向上取整求和
l, r = 1, max(piles)
while l < r:
mid = (l + r) // 2
if hours(mid) <= h: r = mid # 够快 → 保留 mid,试更慢
else: l = mid + 1 # 太慢 → 提速
return lC++
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int l = 1, r = *max_element(piles.begin(), piles.end());
while (l < r) {
int mid = (l + r) / 2;
long hours = 0;
for (int p : piles) hours += (p + mid - 1) / mid; // 向上取整
if (hours <= h) r = mid; // 够快
else l = mid + 1; // 太慢,提速
}
return l;
}
};Java
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int l = 1, r = 0;
for (int p : piles) r = Math.max(r, p);
while (l < r) {
int mid = (l + r) / 2;
long hours = 0;
for (int p : piles) hours += (p + mid - 1) / mid; // 向上取整
if (hours <= h) r = mid; // 够快
else l = mid + 1; // 太慢
}
return l;
}
}套路模板 · 二分答案(在答案区间上二分 + 判定函数)
# 求「满足条件的最小值」、且条件随值单调时套用
def check(x): ... # 判定:速度/容量 x 行不行
l, r = 最小可能, 最大可能
while l < r:
mid = (l + r) // 2
if check(mid): r = mid # 行,收右求更小
else: l = mid + 1 # 不行,加大
return l复杂度
时间复杂度
O(n log max)
max=max(piles);二分 log(max) 次,每次判定 hours 扫一遍 n 堆
空间复杂度
O(1)
只用 l、r、mid 几个变量,hours 现算不存表
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 爱吃香蕉的珂珂 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
核心思路是什么?+
二分答案:速度越大耗时越短(单调);在 [1, max(piles)] 上二分,hours(k)=各堆向上取整小时和;≤h 则 k 可行,找最小可行 k。
为什么 r=mid 不减 1?+
hours(mid)≤h 时 mid 本身可行、可能就是答案,r=mid 保留;hours(mid)>h 时 mid 不可行,l=mid+1 排除。最后 l==r 即最小可行速度。
复杂度多少?+
二分 O(log max(piles)) 轮,每轮判定扫 n 堆 O(n) → 总 O(n log max);只用常数变量 O(1)。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。