Koko Eating Bananas

Problem statement

Koko has n piles of bananas. Pile i has piles[i] bananas. The guards come back in h hours. Each hour she picks one pile and eats k bananas from it. If the pile is smaller than k, she still spends that whole hour and does not start another pile.

Find the smallest integer k such that she can finish every pile before the guards return.

Example:

Input:

piles = [3, 6, 7, 11], h = 8

Expected output:

4

Why: at speed 4 the hours are ceil(3/4) + ceil(6/4) + ceil(7/4) + ceil(11/4) = 1 + 2 + 2 + 3 = 8. Speed 3 needs 10 hours, so 4 is the minimum.

Practice on LeetCode: Koko Eating Bananas

What we are searching for

k is not an index in piles. It is an eating speed. The hours needed at speed k is the sum of ceil(pile / k) over every pile.

That sum only goes down (or stays) as k goes up. If speed k finishes in time, every faster speed does too. If k is too slow, every slower speed is also too slow.

So the feasible speeds look like:

k:        1  2  3  4  5  …  max(piles)
feasible: N  N  N  Y  Y  …  Y

We want the leftmost Y.

Why not try every speed

The slowest she can eat is 1 banana per hour. The fastest useful speed is max(piles) — one pile per hour, and she cannot go faster than that in a way that saves hours (a pile still costs at least one hour).

Trying every k from 1 to max(piles) is O(n · max(piles)). Pile sizes go up to 10^9. That will not run.

Binary search over the speed range is O(n log M), where M is the largest pile.

This is the same “search the answer” pattern as Find the Smallest Divisor Given a Threshold.

Golang Solution

Search k in [1, max(piles)]. For a candidate mid, ask: do the piles finish in at most h hours? If yes, try slower. If no, go faster. The loop is the lower-bound form: high = mid when mid works, low = mid + 1 when it does not. low lands on the smallest feasible speed.

Time: O(n log M)M is max(piles)
Space: O(1)

import "math"

func minEatingSpeed(piles []int, h int) int {
    maxVal := math.MinInt

    for i := 0; i < len(piles); i++ {
        if maxVal < piles[i] {
            maxVal = piles[i]
        }
    }

    low, high := 1, maxVal
    for low < high {
        mid := low + (high-low)/2
        if feasible(mid, piles, h) {
            high = mid
        } else {
            low = mid + 1
        }
    }
    return low
}

func feasible(mid int, piles []int, h int) bool {
    var temp float64
    for i := 0; i < len(piles); i++ {
        temp += math.Ceil(float64(piles[i]) / float64(mid))
    }
    return int(temp) <= h
}

ceil(a / k) is the hours for one pile: she cannot leave a pile mid-hour and start another. h is always at least n (one hour per pile), so a feasible k exists — max(piles) always works.

Integer-only hours, if you want to drop float64: (piles[i] + mid - 1) / mid.