LeetCode 875. 爱吃香蕉的珂珂
题目描述


题意分析
选定一个正整数速度后,每小时只能吃一堆香蕉;即使提前吃完这一堆,本小时也不能继续吃另一堆。求能在
h小时内吃完所有香蕉的最小速度,题目保证h不少于堆数。
解法:二分最小可行速度
核心思路
[!blue]
先考虑如何判断一个速度
k是否可行。大小为pile的一堆需要ceil(pile / k)小时,也就是整数除法(pile + k - 1) / k。每堆最后不足一小时的部分仍独占一个小时,所以必须逐堆向上取整再求和,不能先合并香蕉总数。总耗时不超过h,这个速度就可行;吃堆的先后顺序不影响该总和。随着
k增大,每堆耗时只会减少或不变,总耗时也不会增加。因此速度范围分成“不可行的一段”和“可行的一段”,目标就是找到第一个可行速度,无需逐个尝试。令
M为最大堆大小,答案一定在[1, M]内:速度至少为一;速度达到M时每堆都只需要一小时,而题目保证h >= piles.length,所以右端一定可行。二分始终保留包含最小答案的闭区间
[left, right]。若mid可行,答案不超过它,令right = mid,保留中点继续找更小速度;若mid不可行,那么所有不超过它的速度也不可行,令left = mid + 1。只要left < right,中点就小于右端,两种更新都会缩小区间;边界相等时,剩下的就是最小可行速度。判定中使用
long或int64计算耗时,并在除法前提升类型。累计值一旦超过h就返回失败,因为后面的堆只会继续增加耗时。
解题步骤
- 扫描数组得到最大堆大小,初始化
left = 1、right = M。- 在
left < right时计算mid = left + (right - left) / 2。- 逐堆累计向上取整后的小时数,判断速度
mid能否在h小时内完成。- 可行就令
right = mid,不可行就令left = mid + 1。- 当两端重合时返回
left。若速度一已经可行,搜索自然收缩到一;若每堆只能分到一小时,搜索自然收缩到最大堆大小。
代码实现
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = 0;
for (int pile : piles) {
right = Math.max(right, pile);
}
while (left < right) {
int mid = left + (right - left) / 2;
// 可行速度仍可能就是最小答案,收缩时保留中点。
if (canFinish(piles, h, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
private boolean canFinish(int[] piles, int h, int speed) {
long hours = 0;
for (int pile : piles) {
// 每堆独立向上取整,剩余不足一小时也不能分给另一堆。
hours += (pile + speed - 1L) / speed;
if (hours > h) {
return false;
}
}
return true;
}
}
func minEatingSpeed(piles []int, h int) int {
left, right := 1, 0
for _, pile := range piles {
if pile > right {
right = pile
}
}
for left < right {
mid := left + (right-left)/2
// 可行速度仍可能就是最小答案,收缩时保留中点。
if canFinishBananas(piles, h, mid) {
right = mid
} else {
left = mid + 1
}
}
return left
}
func canFinishBananas(piles []int, h, speed int) bool {
var hours int64
for _, pile := range piles {
// 每堆独立向上取整,剩余不足一小时也不能分给另一堆。
hours += (int64(pile) + int64(speed) - 1) / int64(speed)
if hours > int64(h) {
return false
}
}
return true
}
复杂度分析
- 时间复杂度:$O(n \log(M+1))$。
n是堆数,M是最大堆大小;先扫描数组确定上界,每次二分判定最多扫描n堆,速度区间每轮减半。- 空间复杂度:$O(1)$。只使用边界、中点和累计时间等变量。
关键点总结
[!green]
- 每堆分别向上取整,得到固定速度下所需的准确时间。
- 耗时随速度单调不增,把最优化问题变成寻找第一个可行速度。
- 更新边界时保留所有可能答案,最后一个候选值就是最小可行速度。
易错点总结
[!yellow]
pile / speed会漏算有余数时的最后一小时;对总香蕉数统一取整又会错误地共享各堆的剩余时间。- 左边界不能设为零,否则判定会除零。
- 中点可行不代表它最小,不能立即返回,也不能用
right = mid - 1排除它。- 当前更新方式配套的是
while (left < right);改成left <= right会在单点区间反复保留同一个中点。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1011. 在 D 天内送达包裹的能力 | 中等 | 同样二分最小可行能力并计算所需时间,原题是运输容量,本题是吃香蕉速度。 |
| 1283. 使结果不超过阈值的最小除数 | 中等 | 同样把每项向上取整后的总和作为单调可行性判定。 |
| 410. 分割数组的最大值 | 困难 | 二分答案并用贪心累计检验是否可行;本题约束耗时并最小化处理速度,该题约束分段数量并最小化最大段和。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!