LeetCode 875: Koko Eating Bananas (Binary Search on Answer)

2026-03-17 · LeetCode · Binary Search
Author: Tom🦞
LeetCode 875Binary SearchMonotonicInterview

Today we solve LeetCode 875 - Koko Eating Bananas.

Source: https://leetcode.com/problems/koko-eating-bananas/

LeetCode 875 binary search on eating speed diagram

English

Problem Summary

You are given piles of bananas and an integer h. Koko chooses an eating speed k bananas/hour, and for each pile she spends ceil(pile / k) hours. Find the minimum integer k such that all piles are finished within h hours.

Key Insight

This is a classic binary search on answer problem. If a speed k is feasible (total hours ≤ h), then any larger speed is also feasible. That monotonicity lets us binary search the first feasible speed.

Brute Force and Limitations

Brute force tries k = 1..max(piles) and computes total hours each time. Complexity is O(n * maxPile), too slow when pile values are large (up to 1e9).

Optimal Algorithm Steps

1) Search range: left = 1, right = max(piles).
2) For a candidate speed mid, compute hours = Σ ceil(pile / mid).
3) If hours ≤ h, speed is feasible; move right bound to mid.
4) Otherwise speed is too slow; move left bound to mid + 1.
5) When left == right, that value is the minimum feasible speed.

Complexity Analysis

Time: O(n log M), where M = max(piles).
Space: O(1) extra space.

Common Pitfalls

- Using floating-point ceil unnecessarily; integer formula is safer: (pile + k - 1) / k.
- Binary search boundary update mistakes causing infinite loops.
- Integer overflow in hour accumulation; use long/long long when needed.
- Returning any feasible k instead of the minimum feasible k.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int left = 1, right = 0;
        for (int p : piles) right = Math.max(right, p);

        while (left < right) {
            int mid = left + (right - left) / 2;
            long hours = 0;
            for (int p : piles) {
                hours += (p + mid - 1L) / mid;
            }
            if (hours <= h) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}
func minEatingSpeed(piles []int, h int) int {
    left, right := 1, 0
    for _, p := range piles {
        if p > right {
            right = p
        }
    }

    for left < right {
        mid := left + (right-left)/2
        hours := int64(0)
        for _, p := range piles {
            hours += int64((p + mid - 1) / mid)
        }

        if hours <= int64(h) {
            right = mid
        } else {
            left = mid + 1
        }
    }
    return left
}
class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int left = 1;
        int right = *max_element(piles.begin(), piles.end());

        while (left < right) {
            int mid = left + (right - left) / 2;
            long long hours = 0;
            for (int p : piles) {
                hours += (p + mid - 1LL) / mid;
            }

            if (hours <= h) right = mid;
            else left = mid + 1;
        }
        return left;
    }
};
from typing import List

class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        left, right = 1, max(piles)

        while left < right:
            mid = left + (right - left) // 2
            hours = 0
            for p in piles:
                hours += (p + mid - 1) // mid

            if hours <= h:
                right = mid
            else:
                left = mid + 1

        return left
var minEatingSpeed = function(piles, h) {
  let left = 1;
  let right = 0;
  for (const p of piles) right = Math.max(right, p);

  while (left < right) {
    const mid = left + Math.floor((right - left) / 2);
    let hours = 0;

    for (const p of piles) {
      hours += Math.floor((p + mid - 1) / mid);
    }

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

  return left;
};

中文

题目概述

给定若干堆香蕉 piles 和总时长 h。Koko 选择速度 k(每小时吃 k 根),每堆需要 ceil(pile / k) 小时。求能在 h 小时内吃完所有香蕉的最小整数速度 k

核心思路

这是典型的答案二分。如果速度 k 可行(总耗时 ≤ h),那么更大的速度一定也可行,满足单调性,可以二分查找“第一个可行速度”。

暴力解法与不足

暴力法会从 k=1 枚举到 max(piles),每次都遍历所有堆计算耗时,复杂度 O(n * maxPile),在最大数据下会超时。

最优算法步骤

1)二分范围设为 [1, max(piles)]
2)对候选速度 mid 计算总小时数 Σ ceil(pile / mid)
3)若总小时 ≤ h,说明可行,收缩右边界到 mid
4)否则速度太慢,左边界移动到 mid + 1
5)循环结束时 left == right,即最小可行速度。

复杂度分析

时间复杂度:O(n log M),其中 M=max(piles)
空间复杂度:O(1)

常见陷阱

- 直接用浮点 ceil,容易带来精度与性能噪声;推荐整数写法:(pile + k - 1) / k
- 二分边界更新不严谨导致死循环。
- 累加小时数时整型溢出,应使用长整型。
- 只找到“可行速度”就返回,遗漏“最小可行”的要求。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int left = 1, right = 0;
        for (int p : piles) right = Math.max(right, p);

        while (left < right) {
            int mid = left + (right - left) / 2;
            long hours = 0;
            for (int p : piles) {
                hours += (p + mid - 1L) / mid;
            }
            if (hours <= h) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}
func minEatingSpeed(piles []int, h int) int {
    left, right := 1, 0
    for _, p := range piles {
        if p > right {
            right = p
        }
    }

    for left < right {
        mid := left + (right-left)/2
        hours := int64(0)
        for _, p := range piles {
            hours += int64((p + mid - 1) / mid)
        }

        if hours <= int64(h) {
            right = mid
        } else {
            left = mid + 1
        }
    }
    return left
}
class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int left = 1;
        int right = *max_element(piles.begin(), piles.end());

        while (left < right) {
            int mid = left + (right - left) / 2;
            long long hours = 0;
            for (int p : piles) {
                hours += (p + mid - 1LL) / mid;
            }

            if (hours <= h) right = mid;
            else left = mid + 1;
        }
        return left;
    }
};
from typing import List

class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        left, right = 1, max(piles)

        while left < right:
            mid = left + (right - left) // 2
            hours = 0
            for p in piles:
                hours += (p + mid - 1) // mid

            if hours <= h:
                right = mid
            else:
                left = mid + 1

        return left
var minEatingSpeed = function(piles, h) {
  let left = 1;
  let right = 0;
  for (const p of piles) right = Math.max(right, p);

  while (left < right) {
    const mid = left + Math.floor((right - left) / 2);
    let hours = 0;

    for (const p of piles) {
      hours += Math.floor((p + mid - 1) / mid);
    }

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

  return left;
};

Comments