Problem overview
Koko has n piles of bananas, and piles[i] is the size of pile i. The guards return in h hours. Koko picks a speed of k bananas per hour. Each hour she works on a single pile: she eats k bananas from it, or the whole pile if it has fewer than k, and then the hour is over. A pile of p bananas therefore takes p / k hours, rounded up.
The function minEatingSpeed(piles []int, h int) int returns the smallest integer speed that finishes every pile within h hours.
For piles = [3, 6, 7, 11] and h = 8 the answer is 4. At speed 4 the piles take 1 + 2 + 2 + 3 = 8 hours, just in time. At speed 3 they take 1 + 2 + 3 + 4 = 10 hours, which is too many.
The lesson asks for O(n log m) time, where m is the biggest pile, and O(1) extra space.
Approach: binary search on the answer
The slow way is to try speeds 1, 2, 3, and so on, computing the total hours for each until one fits. With piles up to 10^9 that can mean a billion attempts.
The useful observation is about the shape of the problem. If Koko finishes in time at speed k, she also finishes at any faster speed, because no pile takes longer. If she doesn't finish at speed k, no slower speed works either. So going through the speeds from 1 upward, the answer to "is this fast enough?" is no, no, no, then yes, yes, yes: it flips exactly once. Binary search finds the flip point, even though there is no array of speeds to search. This is binary search on the answer.
The search range is easy to bound. Speed 1 is the slowest sensible speed. The biggest pile is a speed that always works, since every pile then takes one hour and the statement guarantees n <= h. No answer is ever bigger, so the range is 1 to the biggest pile.
Each probe needs the total hours at a speed k. A helper, hours, adds up the hours of every pile, which is O(n). The search makes about log2(m) probes, hence O(n log m).
On [3, 6, 7, 11] with h = 8, the probes are:
l 1 r 11 k 6 hours 6
l 1 r 5 k 3 hours 10
l 4 r 5 k 4 hours 8
Speed 6 is fast enough, so the search looks lower, at 1 to 5. Speed 3 is too slow, so it moves up to 4 to 5. Speed 4 fits in exactly 8 hours, and the search then rules out everything below it.
Step by step
- Start the range.
leftis 1 andrightstarts atpiles[0]. The next two steps raiserightto the biggest pile. - Loop over the piles with
range, discarding the index with_, and setrightto the larger ofrightandpileusing the built-inmax. Afterwardsrightis the biggest pile. - Remember a working speed.
answerstarts atright, which is known to work, so it is valid even before the search starts. - Loop while the range is non-empty. Go has no
while;for left <= rightdoes that job. With<=, a range holding one speed still gets that speed tried. - Pick the middle speed
kasleft + (right-left)/2. Integer division rounds down, and this form never overflows the wayleft + rightcould in a fixed-width integer. - Ask whether
kis fast enough withhours(piles, k) <= h. The helper comes later in the file. - If it is, store
kinanswerand look for something slower by movingrighttok - 1. Store first: the search never comes back tok, and it may be the smallest working speed. - If it isn't,
kand every slower speed are too slow, so movelefttok + 1. - Return
answer. When the loop ends, every speed below it was ruled out. - Write the helper
hours(piles []int, k int) int:totalstarts at 0, then a loop over the piles adds each pile's hours, and the function returnstotal. - Round up with integers only. A pile of
pilebananas takes(pile + k - 1) / khours. Addingk - 1pushes any leftover bananas past the next multiple ofk, while an exact multiple doesn't move.
The solution in Go
func minEatingSpeed(piles []int, h int) int {
left, right := 1, piles[0]
for _, pile := range piles {
right = max(right, pile)
}
answer := right
for left <= right {
k := left + (right-left)/2
if hours(piles, k) <= h {
answer = k
right = k - 1
} else {
left = k + 1
}
}
return answer
}
func hours(piles []int, k int) int {
total := 0
for _, pile := range piles {
total += (pile + k - 1) / k
}
return total
}
The Go details worth knowing:
- Ceiling division. Integer
/truncates, so7 / 3is 2 and forgets the last hour.(7 + 3 - 1) / 3is 3, the right count for a pile of 7 at speed 3, while(6 + 3 - 1) / 3is still 2 for a pile that divides evenly. - The built-in
max. Since Go 1.21,maxandminare built in and work on ordered types such asint. Nothing needs importing. intis 64 bits. The total hours can be huge: 10^4 piles of 10^9 bananas at speed 1 take 10^13 hours. As anintthat prints as10000000000000. In a 32-bit integer the same sum wraps around silently: adding 10^9 ten thousand times in anint32gives 1316134912. Go'sinthas no such problem here, but a type likeint32would.- Ignoring the index.
for _, pile := range pilesuses the blank identifier because Go refuses to compile an unused variable. - Helper order. Go doesn't need
hoursto be declared beforeminEatingSpeeduses it; functions at the top level of a file can appear in any order.
Complexity
Time: O(n log m). Finding the biggest pile is O(n). The binary search halves a range of at most m speeds, so it makes about log2(m) probes: roughly 30 for m = 10^9. Each probe calls hours, which visits every pile once.
Space: O(1). The function keeps a few integers and never copies the piles.
Edge cases and common mistakes
- One pile. With
[5]andh = 1the answer is 5, since the pile must be eaten in one hour. Withh = 5it is 1. - As many hours as piles. When
hequals the number of piles, every pile needs a single hour, so the answer is the biggest pile. For[30, 11, 23, 4, 20]andh = 5that is 30. - A tight fit. For the same piles with
h = 6the answer is 23. At speed 23 the hours are 2 + 1 + 1 + 1 + 1 = 6; at 22 the pile of 23 takes 2 hours too, giving 2 + 1 + 2 + 1 + 1 = 7. - Rounding down per pile. Using
pile / kinstead of the ceiling undercounts the hours, so a too-slow speed looks fast enough and the answer comes out too small. - Rounding the total instead of each pile. Koko never eats from two piles in one hour. Dividing the sum of all bananas by
kignores that. - Losing the answer. Moving
righttok - 1without savingkfirst can throw away the smallest working speed. - Starting
leftat 0. Speed 0 would divide by zero inhours. The range starts at 1. - Huge piles. Two piles of 10^9 with
h = 3give 10^9: at any slower speed, such as 500000000, they take 2 + 2 = 4 hours.
Testing
piles = [3, 6, 7, 11],h = 8gives4.piles = [30, 11, 23, 4, 20],h = 5gives30.piles = [30, 11, 23, 4, 20],h = 6gives23.piles = [5],h = 1gives5; withh = 5it gives1.piles = [1, 1, 1],h = 3gives1.piles = [1000000000, 1000000000],h = 3gives1000000000.- Ten thousand piles of
1000000000:h = 10000gives1000000000, andh = 1000000000gives10000.
