
本文详解 leetcode 第 875 题「koko 吃香蕉」的常见错误——错误地将总香蕉数除以速率估算耗时,忽略了每堆独立计时的约束;并给出基于二分搜索的正确解法及完整实现。
本文详解 leetcode 第 875 题「koko 吃香蕉」的常见错误——错误地将总香蕉数除以速率估算耗时,忽略了每堆独立计时的约束;并给出基于二分搜索的正确解法及完整实现。
这道题的核心在于:Koko 每小时只能选择某一堆香蕉,并从中最多吃掉 k 根;若该堆剩余不足 k 根,则她当小时吃完即止,不跨堆进食。因此,总耗时 ≠ ⌈总香蕉数 / k⌉,而应是各堆单独计算所需小时数之和。
例如,piles = [3,6,7,11], k = 4 时:
- 第一堆 3 根 → 需 1 小时(3 ≤ 4)
- 第二堆 6 根 → 需 2 小时(6 ÷ 4 = 1.5 → 向上取整为 2)
- 第三堆 7 根 → 需 2 小时(7 ÷ 4 = 1.75 → ⌈⌉ = 2)
- 第四堆 11 根 → 需 3 小时(11 ÷ 4 = 2.75 → ⌈⌉ = 3)
✅ 总耗时 = 1 + 2 + 2 + 3 = 8 小时,恰好满足 h = 8。
而原代码中 canEatPiles 方法直接用 Math.ceil((double) sum / capacityPerHour) 计算,得到 ⌈27/4⌉ = 7,严重低估了实际耗时——因为它错误假设 Koko 可以“匀速摊销”所有香蕉,忽略了每堆必须整小时处理的硬性规则。
✅ 正确的耗时计算函数应为:
private int hoursNeeded(int k, int[] piles) {
int totalHours = 0;
for (int pile : piles) {
totalHours += (pile + k - 1) / k; // 等价于 Math.ceil((double) pile / k),避免浮点运算
}
return totalHours;
}
同时,二分搜索边界与终止逻辑也需严谨:
- 左边界 left = 1(最小可能速率)
- 右边界 right = max(piles)(最坏情况:每小时只吃最大堆,1 小时/堆 → 总耗时 = 堆数 ≤ h,一定可行)
- 循环条件 left 最小合法 k
- ❌ 原代码 return mid 在循环外返回,存在未覆盖的边界风险(如 l > r 时 mid 为上一轮值,未必合法)
以下是修正后的完整、通过全部测试用例的 Java 实现:
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = Arrays.stream(piles).max().orElse(1);
while (left <p>? 关键注意事项:</p>
- 勿混淆“总量均摊”与“单堆独立计时”:这是本题唯一也是最大的逻辑陷阱;
- 二分搜索返回值必须是最终 left(或 right),而非中间变量 mid,否则在边界 case 下易出错;
- 使用 (a + b - 1) / b 替代 Math.ceil((double)a/b) 更高效、无精度风险;
- 时间复杂度:O(n log M),其中 M = max(piles),空间复杂度:O(1)。
掌握这一建模本质——将“最小化最大速率”转化为“二分答案 + 可行性验证”——可复用于大量类似问题(如“分割数组的最大值”、“运送包裹的能力”等)。










