LC 875 · Medium · Template C · search on the answer

Koko Eating Bananas

The words "minimum" or "maximum" attached to something that must satisfy a condition, with no sorted array anywhere in sight.

Recognise it

Before any code, what in the question tells you this is the pattern?

No sorted array in sight. Instead: "find the minimum speed / smallest capacity / least X such that a condition is satisfiable." When the answer itself lives in a numeric range and feasibility is monotonic, you binary search the ANSWER, not the input. This is THE pattern interviewers love because it looks nothing like binary search at first.

The brute force

Always have this one ready. It is the honest starting point, and it is what the real solution improves on.

Try every eating speed starting at 1. For each one, add up the hours Koko needs, and return the first speed that fits inside the allowed hours.

O(max(piles) * n) time, since each candidate speed costs a full pass over the piles.

The piles can hold a billion bananas, so the loop over candidate speeds is the expensive part, not the check. That should make you look at the sequence of answers rather than the piles. Speed 1 is too slow, and so is 2, and so is 3, until at some point a speed works and then every larger speed also works. The answers form a run of no followed by a run of yes, and that shape is exactly what binary search consumes.

The approach

The idea in plain language, before it becomes syntax.

The array you binary search is not the input. It is the range of possible answers, from a speed of 1 up to the largest pile, because eating faster than the largest pile cannot help.

For this to be legal the question you ask has to be monotonic. Here the question is whether Koko finishes within the allowed hours at speed k. If she finishes at speed k then she certainly finishes at any speed above k, so once the answer becomes yes it never goes back to no. That single property is what lets you discard half the speeds at a time, and it is the thing to check before reaching for this pattern.

You want the smallest speed that works, which is the boundary between the no block and the yes block. When the middle speed works you keep it as a candidate and search the lower half, because something smaller might also work. When it fails you know the middle and everything below it are out, so you move past it. The loop ends when the range collapses onto a single value, and that value is the answer.

The answer space for Koko, showing a monotonic boundary between too slow and fast enough.There is no sorted array. You search the answer instead.k=1k=maxtoo slowfast enoughthe smallest k that still worksOnce false never turns back to true, the predicate is monotonic and binary search applies.
Koko Eating Bananas: the idea in one picture.edit source
The candidate range halving from n down to one.nn/2n/4n/8... 1log n steps, so a billion elements is about thirty.
The same halving as an array search, over speeds instead of indices.

The algorithm

The same idea again, as steps you could follow with a pencil.

Flowchart for binary searching on the answer with a feasibility check.lo = 1, hi = max(piles)lo < hi ?yesmid = (lo + hi) / 2hours(mid) <= H ?yeshi = midnolo = mid+1noanswer = lohi = mid, not mid - 1. mid may itself be the answer, so it must stay in range.
Koko Eating Bananas: the loop, step by step.edit source
Time O(n · log(max pile))Space O(1)

The solution

Now the code, and why each decision in it is the way it is.

koko_eating_bananas
import math

def minEatingSpeed(piles, h):
    def feasible(k):                       # monotonic in k
        return sum(math.ceil(p / k) for p in piles) <= h

    lo, hi = 1, max(piles)
    while lo < hi:                         # leftmost-true (Template B)
        mid = (lo + hi) // 2
        if feasible(mid):
            hi = mid                       # maybe smaller works
        else:
            lo = mid + 1                   # need to eat faster
    return lo

The hours for one pile are the pile size divided by the speed, rounded up. Integer division rounds down, so write it as (pile + k - 1) / k or use an explicit ceiling. Rounding down here is the most common bug in this problem and it silently reports that Koko is faster than she is.

The condition is lo < hi, not lo <= hi, and the successful branch sets hi = mid rather than mid - 1. That pairing is deliberate. mid might be the answer, so it has to stay inside the range, and lo < hi is what stops that from looping forever.

When the loop ends lo and hi are equal, so returning either one is correct. There is no separate not-found case, because the largest pile is always a working speed.

Hours per pile must round up, not down.7 bananas at speed 37 / 3 = 2wrong: one banana is left over(7 + 3 - 1) / 3 = 3right: the leftovers still cost an hour
Integer division rounds down, which silently makes Koko look faster.
On success the midpoint stays inside the range.hours(mid) <= Hhi = mid - 1throws away a speed that workshi = midmid may be the smallest one that works
Pair hi = mid with lo < hi, or the loop will not end.

Where people slip

  • Prove monotonicity out loud. That is what justifies binary search. "Faster eating never hurts" is the whole argument.
  • Hours for a pile is ceil(pile / k), not pile / k. A pile is finished in a whole number of hours.
  • Use the leftmost-true template (lo < hi, hi = mid) so you converge on the first feasible k.
  • Bounds: lo = 1 (can't eat 0), hi = max(piles) (eating faster than the biggest pile is pointless).

Your notes

What tripped you up here? Write it in your own words.

Where interviewers go next

Solve this one and the follow-up is usually a variation rather than a new idea. Knowing the connection is worth more than memorising each problem.

  • Capacity To Ship Packages in D DaysLC 1011Identical shape. feasible(cap) = can we ship within D days? Search smallest capacity. lo = max(weight), hi = sum(weights).
  • Split Array Largest SumLC 410Minimize the largest subarray sum over k splits. feasible(limit) = "≤ k pieces if each ≤ limit". Same template, classic hard-tagged but easy once you see it.
  • Min Days to Make m BouquetsLC 1482Binary search the day; feasible(day) counts bouquets available by that day.
  • Magnetic Force / Aggressive CowsLC 1552The MAXIMIZE-the-minimum twin. feasible(gap) = can we place all cows ≥ gap apart? Search the largest feasible gap (flip the template direction).