题目描述

✅ LCR 073. 爱吃香蕉的狒狒

image-20260929005712228

image-20260929005712229

题意分析

每小时只能选择一堆香蕉,最多吃掉 k 根;即使这一堆不足 k 根,吃完后也不能在本小时继续吃另一堆。要求找到能够在 h 小时内吃完的最小整数速度。

因此一堆 p 根需要 $\lceil p/k\rceil$ 小时,各堆耗时必须分别向上取整后再相加。题目保证 h 不小于堆数,所以总能找到可行速度。

解法:二分速度并累计耗时

核心思路

[!blue]

对一个给定速度 k,总耗时可以直接算出:

$T(k)=\sum_{p\in piles}\left\lceil\frac{p}{k}\right\rceil$

速度增大时,每堆耗时只会减少或保持不变,所以 T(k) 不增。若某个速度已能按时吃完,所有更大速度也可行;若它太慢,所有更小速度都不可行。这就能二分查找第一个满足 T(k) <= h 的速度。

搜索下界为 1,避免零速度。上界取最大堆数量 M:此时每堆恰好需要一小时,总耗时为堆数,已经不超过 h,所以右端一定可行。速度再大也无法把单堆耗时降到一小时以下。

每轮计算中点速度的总耗时 s。若 s <= h,中点可能就是最慢可行速度,令 right = mid;否则中点及所有更小速度都排除,令 left = mid+1。答案始终留在闭区间中,区间收敛后的唯一值就是最小可行速度。

正整数除法的向上取整可以写成 (p+mid-1)/mid。代码用 64 位整数累加:堆数最多 $10^4$、单堆最多 $10^9$,速度为一时总耗时可能达到 $10^{13}$,不能按单堆的值域选择累计类型。

解题步骤

  1. 扫描数组得到最大堆数量,初始化速度范围 [1,M]。
  2. 在左右端不同的情况下取下中点,重新将累计耗时清零。
  3. 遍历每一堆,累加它在当前速度下所需的完整小时数。
  4. 能在期限内完成就收右端,否则增大左端;最终返回 left。

耗时恰好等于 h 也算可行。若 h 就等于堆数,每堆都只能占一小时,答案是最大堆数量;若所有堆都只有一根,初始搜索范围已经是单点。

代码实现

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int mx = 0;

        for (int pile : piles) {
            mx = Math.max(mx, pile);
        }

        int left = 1;
        int right = mx;

        while (left < right) {
            int mid = (left + right) >>> 1;
            long s = 0;

            for (int pile : piles) {
                s += ((long) pile + mid - 1) / mid;
            }

            if (s <= h) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }

        return left;
    }
}
import (
    "slices"
)

func minEatingSpeed(piles []int, h int) int {
    left, right := 1, slices.Max(piles)
    for left < right {
        mid := (left + right) >> 1
        var s int64
        for _, pile := range piles {
            s += (int64(pile) + int64(mid) - 1) / int64(mid)
        }
        if s <= int64(h) {
            right = mid
        } else {
            left = mid + 1
        }
    }
    return left
}

复杂度分析

  • 时间复杂度:$O(n\log(M+1))$,其中 $n$ 为堆数、$M$ 为最大堆数量。先线性求上界,每轮判定再线性扫描所有堆。
  • 空间复杂度:$O(1)$,只保存速度边界与累计耗时。

关键点总结

[!green]

  • 不能跨堆共享一个小时,决定了必须对每堆单独向上取整。
  • 总耗时对速度单调不增,二分才能一次排除一整段速度。
  • 右端一开始就已证明可行,结束时无需额外验证答案。

易错点总结

[!yellow]

  • 把总香蕉数一次除以速度,会错误地把不同堆的剩余时间合并。
  • 速度下界取零,会使判定出现除零。
  • 将期限内完成写成 s < h,会排除恰好按时完成的最优速度。
  • 用普通 32 位整数累计全部耗时,可能溢出后误判为可行。

相似题目

题目 难度 关联与区别
1011. 在 D 天内送达包裹的能力 中等 同样二分最小可行能力并计算所需时间,原题是运输容量,本题是吃香蕉速度。
1283. 使结果不超过阈值的最小除数 中等 同样把每项向上取整后的总和作为单调可行性判定。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/38436010
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!