MediumBinary Search

Koko Eating Bananas โ€” Solution

Problem

Koko has n piles of bananas and must finish all of them before the guards return in h hours. Each hour she picks exactly one pile and eats up to k bananas โ€” if the pile has fewer than k, she finishes it and the hour ends. Find the minimum eating speed k (bananas per hour) that still lets her finish in time.

  • Input: piles = [3, 6, 7, 11], h = 8
  • Output: 4
  • Explanation: At speed 4, she needs โŒˆ3/4โŒ‰+โŒˆ6/4โŒ‰+โŒˆ7/4โŒ‰+โŒˆ11/4โŒ‰ = 1+2+2+3 = 8 hours, exactly fitting within the limit.

Intuition

As Koko's speed increases, the total hours needed never goes up โ€” it either stays the same or decreases. This monotone property means we don't need to try every speed; we can binary search over the candidate range and efficiently home in on the minimum valid speed.

Approach 1 โ€” Linear Scan

Try every speed from 1 upward, computing total hours at each step, and return the first speed that fits within h hours.

Steps:

  1. Iterate speed from 1 to max(piles)
  2. For each speed, sum ceil(pile / speed) across all piles
  3. Return the first speed where the total is โ‰ค h
1import math
2
3def minEatingSpeed(piles: list[int], h: int) -> int:
4    for speed in range(1, max(piles) + 1):
5        hours_needed = sum(math.ceil(pile / speed) for pile in piles)
6        if hours_needed <= h:
7            return speed
8    return max(piles)
  • Time: O(n ร— max(piles)) โ€” scans up to max(piles) candidate speeds, checking all n piles for each
  • Space: O(1) โ€” only a constant number of variables beyond the input

Approach 2 โ€” Binary Search on Speed (Optimal)

Binary search over the speed range [1, max(piles)]. For each midpoint, compute whether that speed finishes in time. If it does, try slower; if not, try faster.

Steps:

  1. Set low = 1, high = max(piles) (the largest pile eaten in one hour is always sufficient)
  2. While low < high, compute mid = (low + high) // 2
  3. Compute hours_needed = sum(ceil(pile / mid)) across all piles
  4. If hours_needed <= h, mid is valid but might be too fast โ€” set high = mid to preserve it as a candidate
  5. Otherwise mid is too slow โ€” set low = mid + 1 to search faster speeds
  6. Return low โ€” the smallest speed where the condition is satisfied
1import math
2
3def minEatingSpeed(piles: list[int], h: int) -> int:
4    low, high = 1, max(piles)
5
6    while low < high:
7        mid = (low + high) // 2
8        hours_needed = sum(math.ceil(pile / mid) for pile in piles)
9
10        if hours_needed <= h:
11            high = mid  # mid works; try something smaller
12        else:
13            low = mid + 1  # mid is too slow; minimum valid speed is at least mid+1
14
15    return low
  • Time: O(n log M) where M = max(piles) โ€” binary search runs O(log M) rounds, each scanning all n piles
  • Space: O(1) โ€” only a constant number of variables beyond the input

Complexity Summary

ApproachTimeSpaceWhen to use
Linear ScanO(n ร— M)O(1)Never โ€” max pile can be 10โน, making this infeasible
Binary SearchO(n log M)O(1)Always โ€” this is the intended approach

Common Mistakes

  • Using pile // k instead of ceil(pile / k): Integer division undercounts hours โ€” a pile of 7 at speed 5 takes 2 hours, not 1. Use (pile + k - 1) // k for exact integer ceiling, or math.ceil(pile / k) in Python.
  • Setting high = mid - 1 when hours fit: Since mid itself may be the minimum valid speed, shrinking to mid - 1 discards the answer. The correct update is high = mid to keep the candidate in range.
  • Setting high = sum(piles): Technically valid but the answer is never larger than max(piles) โ€” at that speed even the biggest pile takes just one hour. The unnecessarily wide range wastes O(log(sum) โˆ’ log(max)) rounds.
  • Not using long for hoursNeeded in Java/C++: At speed 1 the total hours equals sum(piles), which can reach 10โด ร— 10โน = 10ยนยณ โ€” far beyond a 32-bit int. Accumulate into a long / long long.
  • Returning high instead of low: Because the loop exits when low == high, both hold the same value โ€” but in variants where they diverge at exit, returning the wrong one gives an off-by-one. Reasoning about which side of the invariant holds the answer avoids this.

Related Problems

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’