题目描述
思路解析
一句话答案:LeetCode 875 爱吃香蕉的珂珂用二分答案:吃速越大耗时越短这条单调性,让判定函数 hours(k)=各堆向上取整之和能二分找最小可行速度。时间 O(n·log(max))、空间 O(1)。
珂珂每小时只吃一堆,这道题到底在求哪个速度
给几堆香蕉 piles 和警卫回来前的小时数 h,珂珂每小时挑一堆、以固定速度 k 根吃,一堆吃光了剩下时间也不换堆。求能在 h 小时内吃完全部香蕉的最小整数速度 k。题面 piles=[3,6,7,11]、h=8 时答案是 4:速度 4 刚好 8 小时吃完,再慢一档就超时。
为什么从 1 到最大堆一个个试速度会白扫一大截
速度最小取 1、最大取 11(最大那堆 11 根,速度 11 一小时吃光,再快没意义)。一个笨办法是从 1 起逐个速度试,每试一个就把四堆各要几小时加起来、看超不超 h。可最坏要一路试到最大那档才停,扫过的速度数和这段速度范围(1 到最大堆)一样多,时间 O(n·max);范围大到 10⁹ 就挨个试不动了。
速度和「来不来得及」之间,藏着一条能二分的分界线
把每个速度的总小时数排一排会看出:速度越大,吃完的总时间只会越短、越来得及——这就是单调(沿一个方向走、不回头)。于是所有速度被切成两半,左边都太慢、来不及,右边都够快、来得及,答案正是界线上第一个够快的速度。有了单调分界,就能用二分查找逼近它,不必挨个试。这里点破一句:二分的不是某个数组下标,而是所有可能的吃速。这种不直接推公式、而是猜一个速度再验证行不行的思路,叫二分答案。
判定函数怎么写,够快和太慢又各往哪半收
先把「行不行」做成判定函数(输入一个速度、回答够不够快的小函数)hours(k):每堆各要 ⌈香蕉数 ÷ k⌉ 小时(⌈⌉ 是向上取整,吃不满一小时也算一整小时),加起来就是这速度的总耗时。二分在区间 [lo, hi] 取中点 mid 算 hours(mid):≤ h 够快,把 hi 收到 mid(mid 可能正是最小,得留);> h 太慢,mid 连左边全不行,把 lo 跳到 mid+1。缩到 lo 和 hi 撞上,那格就是最小可行速度。
拿题面 piles、h=8 逐轮列出 mid 和判定
起点 lo=1、hi=11。第一轮 mid=(1+11)//2=6,hours(6)=1+1+2+2=6 ≤ 8 够快,hi 收到 6。第二轮 lo=1、hi=6,mid=3,hours(3)=1+2+3+4=10 > 8 太慢,lo 跳到 4。第三轮 lo=4、hi=6,mid=5,hours(5)=1+2+2+3=8 ≤ 8 够快,hi 收到 5。第四轮 lo=4、hi=5,mid=4,hours(4)=1+2+2+3=8 ≤ 8 够快,hi 收到 4。此时 lo=hi=4,循环停,返回 4。
lo 起点搁 0,判定里的除法当场崩
二分把速度档数压到约 log max,每档 hours() 扫 n 堆,时间 O(n·log max)、空间 O(1)。lo 起点得从 1 起,写成 0 时 ⌈p ÷ 0⌉ 当场除零崩掉。够快时收缩只能写 hi=mid,写成 hi=mid-1 会把可能就是答案的 mid 丢出区间,返回值偏大一档。太慢那支要写 lo=mid+1,若写成 lo=mid,等 lo、hi 只差一格时 mid 永远卡在 lo、区间缩不动,就死循环。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条「够快就试更慢、太慢就加速」,下面每一帧都在套它。
速度最小取 1,最大取 11(最大那堆 11 根,速度 11 时一小时吃光,再快没意义)。l、r 框住当前要搜的速度区间。
速度 1:27 小时 > 8,来不及,cur 往右挪一格试更快的。逐个扫最坏要试到 11——下面用二分把它压到 log 次。
速度 2:15 小时 > 8,来不及,cur 往右挪一格试更快的。逐个扫最坏要试到 11——下面用二分把它压到 log 次。
速度 3:10 小时 > 8,来不及,cur 往右挪一格试更快的。逐个扫最坏要试到 11——下面用二分把它压到 log 次。
速度 4:8 小时 ≤ 8,来得及。注意从这格往右每个速度都来得及(速度越快越省时)——左边来不及、右边来得及,是一条单调分界线。逐个扫最坏要试 11 次,太慢。
速度 5:8 小时 ≤ 8,来得及。注意从这格往右每个速度都来得及(速度越快越省时)——左边来不及、右边来得及,是一条单调分界线。逐个扫最坏要试 11 次,太慢。
速度 6:6 小时 ≤ 8,来得及。注意从这格往右每个速度都来得及(速度越快越省时)——左边来不及、右边来得及,是一条单调分界线。逐个扫最坏要试 11 次,太慢。
在区间 [1..11] 取中点速度 6:每堆各算 ⌈香蕉数 / 6⌉ 小时,合计 6 小时。
够快(6 ≤ 8)。既然 6 都来得及,比它更快的速度只会更快、更不缺这道答案的「最小」要求——右半边整段排除。但 6 自己可能正是最小答案,要留着。
因为够快,r 收到 mid(保留 6 这个候选),继续在更慢一侧找有没有更小的可行速度。区间从 11 格砍到 6 格。
在区间 [1..6] 取中点速度 3:每堆各算 ⌈香蕉数 / 3⌉ 小时,合计 10 小时。
太慢(10 > 8)。3 都吃不完,比它还慢的更不行——左半边连同 mid 一起排除,只能去更快的一侧。
因为太慢,l 跳到 mid 右边一格(4),把不可行的全甩掉,继续在更快一侧找。区间从 6 格砍到 3 格。
在区间 [4..6] 取中点速度 5:每堆各算 ⌈香蕉数 / 5⌉ 小时,合计 8 小时。
够快(8 ≤ 8)。既然 5 都来得及,比它更快的速度只会更快、更不缺这道答案的「最小」要求——右半边整段排除。但 5 自己可能正是最小答案,要留着。
因为够快,r 收到 mid(保留 5 这个候选),继续在更慢一侧找有没有更小的可行速度。区间从 3 格砍到 2 格。
在区间 [4..5] 取中点速度 4:每堆各算 ⌈香蕉数 / 4⌉ 小时,合计 8 小时。
够快(8 ≤ 8)。既然 4 都来得及,比它更快的速度只会更快、更不缺这道答案的「最小」要求——右半边整段排除。但 4 自己可能正是最小答案,要留着。
因为够快,r 收到 mid(保留 4 这个候选),继续在更慢一侧找有没有更小的可行速度。区间从 2 格砍到 1 格。
l 和 r 撞到一起,这一格就是答案:最小可行速度 4。它能在 8 小时内吃完(8 小时),再慢一档(速度 3)就要 10 小时、超时。对比暴力要试到第几个,二分只试了 4 次。
边界先想清:h 等于堆数时被逼到速度=最大堆,是上界。
两个高频追问,记住「单调判定 → 二分答案」这个识别套路。
参考代码
import mathdef minEatingSpeed(piles, h): def hours(k): # 速度 k 吃完要几小时 return sum(math.ceil(p / k) for p in piles) lo, hi = 1, max(piles) # 速度区间 [1, max] while lo < hi: mid = (lo + hi) // 2 if hours(mid) <= h: # 够快 hi = mid # 试更慢 else: # 太慢 lo = mid + 1 # 加速 return lo # 最小可行速度复杂度
- 时间:O(n·log(max)),二分速度 log(max) 轮,每轮 hours() 扫一遍 n 堆
- 空间:O(1),只用 lo/hi/mid 几个变量,不开额外数组
易错点
面试追问把动画讲成自己的话
追问为什么这道题能用二分?它看起来不是有序数组。
追问怎么判断一道题适合二分答案?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
寻找旋转排序数组中的最小值
LeetCode 153 · 中等 · 沿着 二分查找 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题