TL;DR
Binary search on the answer (minimum feasible speed) with an O(n) feasibility check β O(n log m) time where m = max(piles), O(1) space.
Approach 1 β Brute force (try every speed)
Check k = 1, 2, 3, β¦ and return the first speed whose total eating time fits within h. A pile of p bananas at speed k takes ceil(p / k) hours, since a final partial hour still consumes a whole hour.
import math
class Solution:
def minEatingSpeed(self, piles: list[int], h: int) -> int:
k = 1
while True:
hours = sum(math.ceil(p / k) for p in piles)
if hours <= h:
return k
k += 1
Time O(n * m) where m = max(piles): up to 10^4 * 10^9 = 10^13 operations when the answer is large β dead on arrival. Space O(1).
Approach 2 β Binary search on the answer
The insight: feasibility is monotone in k. Eating faster never hurts: if speed k finishes in time, every speed above k does too. So the k-axis splits into an infeasible prefix and a feasible suffix, and the answer is the boundary β the first feasible k. Thatβs a boundary-finding binary search over the answer space [1, max(piles)] (at max(piles) every pile takes one hour, and h >= n makes that feasible), with each probe answered by an O(n) simulation. This is the classical binary search on the answer technique: search candidate answers, not data.
class Solution:
def minEatingSpeed(self, piles: list[int], h: int) -> int:
def hours_at(k: int) -> int:
return sum((p + k - 1) // k for p in piles) # ceil(p / k)
lo, hi = 1, max(piles)
while lo < hi:
mid = (lo + hi) // 2
if hours_at(mid) <= h:
hi = mid # feasible β try a slower speed
else:
lo = mid + 1 # too slow β must speed up
return lo
Walkthrough on piles = [3, 6, 7, 11], h = 8:
lo=1, hi=11 β mid=6: hours = 1+1+2+2 = 6 β€ 8 β feasible, hi=6.
lo=1, hi=6 β mid=3: hours = 1+2+3+4 = 10 > 8 β lo=4.
lo=4, hi=6 β mid=5: hours = 1+2+2+3 = 8 β€ 8 β hi=5.
lo=4, hi=5 β mid=4: hours = 1+2+2+3 = 8 β€ 8 β hi=4.
lo == hi == 4 β return 4. β
Time O(n log m) β about 30 probes of an O(n) check at m = 10^9. Space O(1).
Approach 3 β Binary search with integer-ceiling notes (same search, sharper check)
The insight: the feasibility check is where correctness usually leaks, so make the arithmetic exact. (p + k - 1) // k and -(-p // k) both compute ceil(p / k) in pure integers; math.ceil(p / k) routes through a float, which is safe at these bounds but a habit that breaks once numerators exceed 2^53. You can also stop a probe early once the running total exceeds h.
class Solution:
def minEatingSpeed(self, piles: list[int], h: int) -> int:
def feasible(k: int) -> bool:
total = 0
for p in piles:
total += -(-p // k) # exact integer ceil
if total > h: # early exit: already too slow
return False
return True
lo, hi = 1, max(piles)
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid):
hi = mid
else:
lo = mid + 1
return lo
Walkthrough on piles = [30, 11, 23, 4, 20], h = 5: at mid=15 the hours run 2+1+2+1+2 = 8 > 5 (infeasible β lo rises); the search climbs until k=30, where hours = 1+1+1+1+1 = 5 β€ 5, and no smaller k is feasible since k=29 makes the 30-pile cost 2 hours (total 6). Returns 30. β
Same asymptotics as Approach 2 β O(n log m) time, O(1) space β with a constant-factor win from early exit and float-free math.
Common pitfalls
- Computing hours as
p // k (floor) instead of ceil β a 7-banana pile at k=4 takes 2 hours, not 1; leftover bananas still cost a full hour.
- Searching
[1, sum(piles)] or beyond: speeds above max(piles) change nothing (each pile already takes one hour), so hi = max(piles) is the tight, correct cap.
- Using
hi = mid - 1 on the feasible branch β the minimum feasible speed may be mid; this search needs the first-True convention (hi = mid, loop while lo < hi).
- Believing greedy pile-ordering matters: the total is a sum of per-pile ceilings, independent of the order Koko eats them β donβt simulate schedules.
Pattern takeaway
βMinimize k such that the task fits a budgetβ is the signature of binary search on the answer: pick the answer axis, prove feasibility is monotone along it, write a cheap feasible(k) check, and binary search for the first True. The data structure is irrelevant β the sorted thing is the answer space itself. This template dispatches a whole family of problems (ship packages within D days, split array largest sum, minimum days to make bouquets).