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

More Binary Search solutions

View all โ†’

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 โ†’