875. 爱吃香蕉的珂珂

中等 含交互动画

学完这道你应该能: 说清它为什么用「二分答案」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

每堆香蕉 piles[i] 根,每小时最多吃一堆里 k 根(吃不完也得等下一小时)。要在 h 小时内吃完,求最小速度 k。

piles = [3, 6, 7, 11]
h = 8
输出 = 4

思路解析

最直接:速度 = 1、2、3… 挨个试,第一个「能在 h 小时内吃完」的就是答案。最坏要试到 max(piles) 那么大,香蕉堆里有上亿根就试上亿次。但这里藏着规律——速度越大,耗时只会越短、绝不会忽长忽短,这种单调性正是二分的信号。

转折:与其挨个试速度,不如在速度区间 [1, max(piles)] 上二分。先写个判定函数 hours(k):逐堆 ⌈piles[i]/k⌉ 求和,得到吃完要几小时。不变量:hours(k) 随 k 增大单调递减。对中间速度 mid,hours(mid) ≤ h 说明够快、还能更慢(收右);大于 h 说明太慢(收左)——照样每步砍一半。

速度区间 [1, 11]:把候选速度排成一排(1~11),左指针 l=速度1,右指针 r=速度11。注意:二分的对象是速度,不是香蕉堆;每个速度够不够快,靠判定函数 hours 算。

第 1 轮 · 取 mid:中间下标取速度 6。先别急着收缩,用判定函数算算速度 6 到底要几小时。

第 1 轮 · 判定(够快):速度 6:每堆向上取整算小时,1+1+2+2 = 6 小时 ≤ 8,够快!它是个可行解,但说不定还能更慢,所以保留它、往左找更小的。

第 1 轮 · 收缩:速度 7~11(更快但更浪费)整段灰掉丢弃,r 收到 mid(不减 1,速度6 自己可能就是答案)。范围缩到速度 [1, 6]。

第 2 轮 · 取 mid:新范围 [1, 6],中间取速度 3。继续用判定函数算它要几小时。

第 2 轮 · 判定(太慢·负例):速度 3:1+2+3+4 = 10 小时,大于 8,超时了,不可行。说明速度 3 以及比它更慢的都不行,得加速——这一半要丢掉。

第 2 轮 · 收缩:速度 1、2、3(含 mid)都太慢,整段灰掉,l 跳到 mid+1(速度4)。范围缩到速度 [4, 6]。

第 3 轮 · 取 mid 并判定:范围 [4, 6],中间速度 5:1+2+2+3 = 8 小时,正好 ≤ 8,够快。保留它往左找,r 收到 mid(速度5)。范围缩到速度 [4, 5]。

第 4 轮 · 取 mid 并判定:范围 [4, 5],中间速度 4:也是 8 小时 ≤ 8,够快。r 收到 mid(速度4),此时 l == r,区间只剩速度 4,循环结束。

收敛 · 答案:l 和 r 撞在速度 4——这就是能在 8 小时内吃完的最小速度。再慢一档(速度3)就超时。

「求最小/最大的、满足某条件的值」,且条件随值单调,就能二分答案:x 的平方根、D 天送货都是它。

参考代码(Python)

Python
1import math
2def minEatingSpeed(self, piles, h):
3    def hours(k):                     # 判定函数:速度 k 要几小时
4        return sum(math.ceil(p / k) for p in piles)
5    l, r = 1, max(piles)              # 在速度区间上二分
6    while l < r:                      # l==r 时即答案
7        mid = (l + r) // 2
8        if hours(mid) <= h:
9            r = mid                  # 够快,收右试更小
10        else:
11            l = mid + 1               # 太慢,加速
12    return l

复杂度分析

  • 时间复杂度:O(n log max) —— max=max(piles);二分 log(max) 次,每次判定 hours 扫一遍 n 堆
  • 空间复杂度:O(1) —— 只用 l、r、mid 几个变量,hours 现算不存表

套路模板

记住骨架:二分的是答案区间、核心是写对 check、合法时收右求最小。求最大值就把收缩方向反过来。

Python
1# 求「满足条件的最小值」、且条件随值单调时套用
2def check(x): ...                      # 判定:速度/容量 x 行不行
3l, r = 最小可能, 最大可能
4while l < r:
5    mid = (l + r) // 2
6    if check(mid): r = mid            # 行,收右求更小
7    else: l = mid + 1                 # 不行,加大
8return l

易错点

  • 错误写法:在 piles 上二分、上界乱填正确写法:在速度区间 [1, max(piles)] 上二分(二分的对象是答案(速度),不是数组;上界必须取 max(piles),因为最快也只需每小时吃完最大那堆,再大没意义)
  • 错误写法:hours(mid) <= h 时写 l = mid+1正确写法:此时 r = mid(往更小速度收)(我们要的是满足条件的最小值,够快时应保留 mid 并往左找,方向写反会得到偏大答案)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →289. 生命游戏 ← 返回题库