Binary Search · Medium
LeetCode 875: Koko Eating Bananas — solution
Binary search on the answer: the eating speed is monotonic (faster always finishes sooner), so search the smallest speed that fits in h hours. O(n log m) time, where m is the largest pile, and O(1) space.
Problem in plain words
There are several piles of bananas and a deadline of h hours. Each hour, Koko picks one pile and eats up to k bananas from it. If the pile has fewer than k left she finishes it and waits for the next hour; she never moves to another pile within the same hour. Find the smallest whole number k that lets her finish every pile within h hours.
For instance, with piles [3, 8] and h = 4, speed 3 needs 1 + 3 = 4 hours and works, while speed 2 needs 2 + 4 = 6 hours and fails, so the answer is 3.
Intuition
For a fixed speed k, a pile of size p takes ceil(p / k) hours, and the total is just the sum over all piles. That total can only go down (or stay the same) as k grows. So the speeds split into a 'too slow' region followed by a 'fast enough' region, and we want the first speed in the second region.
Any monotonic yes/no question over a range of integers is a job for binary search. The range is 1 to max(piles): speed 1 is the slowest possible, and anything above the biggest pile is wasted because each pile already takes exactly one hour at that speed.
Approach
- Set
lo = 1andhi = max(piles). Speedhialways works becauseh >= piles.lengthis guaranteed. - While
lo < hi, takemid = lo + (hi - lo) / 2and compute the hours needed at that speed: the sum of(p + mid - 1) / midover all piles. - If the hours fit (
hours <= h),midis a candidate, so keep it:hi = mid. - Otherwise
midis too slow:lo = mid + 1. - When the pointers meet,
lois the minimum valid speed.
Dry run
Input: piles = [4, 9, 6], h = 5. Search range is 1 to 9.
| lo | hi | mid | Hours at mid | Fits? | Move |
|---|---|---|---|---|---|
| 1 | 9 | 5 | 1 + 2 + 2 = 5 | yes | hi = 5 |
| 1 | 5 | 3 | 2 + 3 + 2 = 7 | no | lo = 4 |
| 4 | 5 | 4 | 1 + 3 + 2 = 6 | no | lo = 5 |
| 5 | 5 | - | - | - | return 5 |
Java solution
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int lo = 1, hi = 0;
for (int p : piles) hi = Math.max(hi, p);
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (hoursNeeded(piles, mid) <= h) {
hi = mid; // fast enough, try slower
} else {
lo = mid + 1; // too slow
}
}
return lo;
}
private long hoursNeeded(int[] piles, int speed) {
long hours = 0;
for (int p : piles) {
hours += (p + (long) speed - 1) / speed; // ceil(p / speed)
}
return hours;
}
}Python solution
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
lo, hi = 1, max(piles)
while lo < hi:
mid = (lo + hi) // 2
hours = sum((p + mid - 1) // mid for p in piles)
if hours <= h:
hi = mid # fast enough, try slower
else:
lo = mid + 1 # too slow
return loComplexity
| Measure | Cost | Why |
|---|---|---|
| Time | O(n log m) | About log2(max pile) iterations, each summing over n piles. |
| Space | O(1) | Only counters; no extra arrays. |
Edge cases and common mistakes
- Integer overflow: with up to 10^4 piles of up to 10^9 bananas, the total hours at speed 1 can exceed the
intrange. Accumulate in alongin Java. - Ceiling division:
p / krounds down. Use(p + k - 1) / korMath.ceilon doubles (the integer form is safer and faster). - Starting `lo` at 0: speed 0 causes division by zero. The lowest meaningful speed is 1.
- `h == piles.length`: each pile gets exactly one hour, so the answer is
max(piles). The search handles this naturally. - Returning the first speed that fits: a linear scan from 1 upward is correct but O(n * m), which is far too slow for large piles.
›How do I recognise 'binary search on the answer' problems?
Ask whether you can cheaply check a guess, and whether checks flip from false to true exactly once as the guess grows. Minimum capacity, minimum speed and minimum maximum-load problems almost always fit.
›Could the upper bound be tighter than max(piles)?
Yes, but it does not matter much: the search is logarithmic, so a bound of 10^9 still needs only about 30 iterations.
