题目描述
每堆香蕉 piles[i] 根,每小时最多吃一堆里 k 根。要在 h 小时内吃完,求最小速度 k。
思路解析
最直接:速度 = 1、2、3… 挨个试,第一个「能在 h 小时内吃完」的就是答案。最坏要试到 max(piles) 那么大,香蕉堆里有上亿根就试上亿次。但有个规律——速度越大,耗时只会越短、不会忽长忽短,这种单调性正是二分的信号。
在速度区间 [1, max(piles)] 里二分:对每个候选速度 mid,算「吃完要几小时」。≤ h 说明 mid 够快,还能更慢,往左找;> h 说明太慢,往右找。
二分答案的关键是先写一个判定函数:输入速度 k,逐堆 ⌈piles[i]/k⌉ 求和,得到吃完要几小时。例如速度 6 → 1+1+2+2 = 6 小时。有了它,给任意速度都能一秒判断「够不够快」,再拿它和 h 比较来决定往左还是往右。
速度区间 [1, 11]:把候选速度排成一排(1~11),左指针 l=速度1,右指针 r=速度11。注意:二分的对象是速度,不是香蕉;判断靠上一步的判定函数。
mid=6 · 算耗时:试速度 6:每堆向上取整算小时,共 6 小时 ≤ 8,够快!记下它,再试更小的速度,r 左移。
mid=3 · 太慢:区间缩到 [1,6],试中间速度 3:要 10 小时 > 8,太慢了。speed 要更大,l 右移到 4。
mid=5 · 够快:区间 [4,6],试速度 5:正好 8 小时 ≤ 8,够快。继续找更小的,r 左移。
mid=4 · 够快:试速度 4:也是 8 小时 ≤ 8,够快。r 左移后区间只剩速度 4。
收敛:区间收敛到速度 4——这就是能 8 小时吃完的最小速度。
「求最小/最大的满足某条件的值」,且条件随值单调,就能二分答案:x 的平方根、送货能力都是它。
参考代码(Python)
1def hours(k): # 判定函数:速度k要几小时
2 return sum(math.ceil(p/k) for p in piles)
3left, right = 1, max(piles)
4while left < right:
5 mid = (left + right) // 2
6 if hours(mid) <= h: right = mid # 够快,试更小
7 else: left = mid + 1 # 太慢,加速
8return left复杂度分析
- 时间复杂度:O(n·log(maxPile)) —— 二分 log 次,每次判定 O(n)
- 空间复杂度:O(1) —— 常数
套路模板
记住骨架:二分的是答案区间、核心是写对 check、合法时收右边求最小。求最大值就把收缩方向反过来。x 的平方根、D 天送货都是它。
1# 求「满足条件的最小值」、且条件随值单调时套
2def check(x): ... # 判定:速度/容量 x 行不行
3left, right = 最小可能, 最大可能
4while left < right:
5 mid = (left + right) // 2
6 if check(mid): right = mid # 行,收右边试更小
7 else: left = mid + 1 # 不行,加大
8return left易错点
- ❌ 在 piles 上二分 → ✅ 在「速度区间 [1, max]」上二分(二分的对象是答案(速度),不是数组)
- ❌ 判定函数方向写反 → ✅ 耗时 ≤ h 时往「更小速度」找(我们要满足条件里的最小值)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。