875. Koko Eating Bananas
Problem Description
Koko has n piles of bananas in front of her, where the i-th pile contains piles[i] bananas. She needs to eat all the bananas before the guards return in h hours.
Koko eats bananas at a constant speed of k bananas per hour. During each hour, she follows these rules:
- She picks one pile to eat from
- If the pile has
kor more bananas, she eats exactlykbananas from it - If the pile has fewer than
kbananas, she eats all remaining bananas in that pile and doesn't eat any more bananas during that hour (even if there's time left)
The goal is to find the minimum eating speed k (bananas per hour) that allows Koko to finish all bananas within h hours.
For example, if piles = [3, 6, 7, 11] and h = 8:
- If
k = 4, Koko needs: 1 hour for pile 1 (3 bananas), 2 hours for pile 2 (6 bananas), 2 hours for pile 3 (7 bananas), and 3 hours for pile 4 (11 bananas), totaling 8 hours - If
k = 3, she would need 10 hours total, which exceeds the limit
The problem uses binary search because eating speeds have a monotonic property: if Koko can finish at speed k, she can also finish at any speed greater than k. The solution searches for the minimum valid speed between 1 and max(piles) using the helper function check(k) which calculates whether all bananas can be eaten within h hours at speed k. The expression (x + k - 1) // k computes the ceiling division to determine hours needed for each pile.
Intuition
The key insight is recognizing that this problem has a monotonic property: if Koko can eat all bananas at speed k within h hours, then she can definitely eat them at any speed faster than k. Conversely, if she cannot finish at speed k, then she cannot finish at any speed slower than k either.
This monotonic behavior immediately suggests binary search as a solution strategy. We need to find the minimum value of k that satisfies our constraint - a classic "find the leftmost valid value" binary search problem.
The search space for k is bounded. The minimum possible speed is 1 (eating one banana per hour), and the maximum useful speed is max(piles) - eating any faster than the largest pile size provides no benefit since Koko can only eat from one pile per hour anyway.
For any given speed k, we can calculate how many hours Koko needs to finish all piles. For a pile with x bananas:
- If
xis divisible byk, she needs exactlyx / khours - If not, she needs
x // k + 1hours (the ceiling ofx / k)
The formula (x + k - 1) // k elegantly computes the ceiling division without using floating-point arithmetic. This is equivalent to math.ceil(x / k) but avoids potential precision issues.
By using binary search on the range [1, max(piles)] and checking if each candidate speed allows Koko to finish within h hours, we can efficiently find the minimum valid speed in O(n * log(max(piles))) time, where n is the number of piles.
Learn more about Binary Search patterns.
Solution Implementation
1from typing import List
2
3class Solution:
4 def minEatingSpeed(self, piles: List[int], h: int) -> int:
5 """
6 Find the minimum eating speed to consume all banana piles within h hours.
7
8 Args:
9 piles: List of integers representing the number of bananas in each pile
10 h: Maximum hours available to eat all bananas
11
12 Returns:
13 Minimum integer eating speed (bananas per hour) to finish all piles
14 """
15
16 def feasible(eating_speed: int) -> bool:
17 """
18 Check if all piles can be consumed within h hours at given eating speed.
19 Returns True if eating_speed is fast enough to finish in time.
20 """
21 total_hours = sum((pile_size + eating_speed - 1) // eating_speed
22 for pile_size in piles)
23 return total_hours <= h
24
25 # Binary search for the minimum valid eating speed
26 # Search space: [1, max(piles)]
27 left, right = 1, max(piles)
28 first_true_index = -1
29
30 while left <= right:
31 mid = (left + right) // 2
32 if feasible(mid):
33 # mid is fast enough, record it and try smaller speeds
34 first_true_index = mid
35 right = mid - 1
36 else:
37 # mid is too slow, need faster speed
38 left = mid + 1
39
40 return first_true_index
411class Solution {
2 public int minEatingSpeed(int[] piles, int h) {
3 // Find maximum pile size for upper bound
4 int maxPile = 0;
5 for (int pile : piles) {
6 maxPile = Math.max(maxPile, pile);
7 }
8
9 // Binary search bounds: [1, maxPile]
10 int left = 1;
11 int right = maxPile;
12 int firstTrueIndex = -1;
13
14 while (left <= right) {
15 int mid = left + (right - left) / 2;
16
17 // Check if speed 'mid' is feasible (can finish within h hours)
18 int totalHours = 0;
19 for (int pile : piles) {
20 totalHours += (pile + mid - 1) / mid;
21 }
22
23 if (totalHours <= h) {
24 // Feasible: record answer and try smaller speed
25 firstTrueIndex = mid;
26 right = mid - 1;
27 } else {
28 // Not feasible: need faster speed
29 left = mid + 1;
30 }
31 }
32
33 return firstTrueIndex;
34 }
35}
361class Solution {
2public:
3 int minEatingSpeed(vector<int>& piles, int h) {
4 // Binary search range: [1, max(piles)]
5 int left = 1;
6 int right = ranges::max(piles);
7 int firstTrueIndex = -1;
8
9 while (left <= right) {
10 int mid = left + (right - left) / 2;
11
12 // Check if speed 'mid' is feasible
13 int totalHours = 0;
14 for (int pile : piles) {
15 totalHours += (pile + mid - 1) / mid;
16 }
17
18 if (totalHours <= h) {
19 // Feasible: record answer and try smaller speed
20 firstTrueIndex = mid;
21 right = mid - 1;
22 } else {
23 // Not feasible: need faster speed
24 left = mid + 1;
25 }
26 }
27
28 return firstTrueIndex;
29 }
30};
311/**
2 * Finds the minimum eating speed to consume all piles within h hours
3 * Uses binary search to find the optimal speed
4 * @param piles - Array of pile sizes
5 * @param h - Maximum hours allowed to eat all piles
6 * @returns Minimum eating speed (bananas per hour)
7 */
8function minEatingSpeed(piles: number[], h: number): number {
9 // Binary search range: [1, max(piles)]
10 let left: number = 1;
11 let right: number = Math.max(...piles);
12 let firstTrueIndex: number = -1;
13
14 while (left <= right) {
15 const mid: number = Math.floor((left + right) / 2);
16
17 // Check if speed 'mid' is feasible
18 const totalHours: number = piles
19 .map((pileSize: number) => Math.ceil(pileSize / mid))
20 .reduce((acc: number, cur: number) => acc + cur, 0);
21
22 if (totalHours <= h) {
23 // Feasible: record answer and try smaller speed
24 firstTrueIndex = mid;
25 right = mid - 1;
26 } else {
27 // Not feasible: need faster speed
28 left = mid + 1;
29 }
30 }
31
32 return firstTrueIndex;
33}
34Solution Approach
The solution implements binary search to find the minimum eating speed. Here's how the algorithm works:
1. Set up the binary search boundaries:
- Left boundary
l = 1(minimum possible eating speed) - Right boundary
r = max(piles)(maximum useful eating speed)
2. Define the check function:
The helper function check(k) determines if Koko can finish all bananas at speed k within h hours:
def check(k: int) -> bool:
return sum((x + k - 1) // k for x in piles) <= h
- For each pile
x, calculate hours needed:(x + k - 1) // k(ceiling division) - Sum all hours needed across all piles
- Return
Trueif total hours ≤h, otherwiseFalse
3. Apply binary search:
The code uses Python's bisect_left function in an elegant way:
return 1 + bisect_left(range(1, max(piles) + 1), True, key=check)
This works because:
bisect_leftsearches for the leftmost position whereTruecan be inserted in a sorted sequence- The
key=checkparameter transforms each speed value into a boolean (can Koko finish in time?) - For speeds too slow,
checkreturnsFalse; for speeds fast enough, it returnsTrue - Since we want speeds that work (
Truevalues), andFalse < Truein Python, the sequence looks like:[False, False, ..., False, True, True, ..., True] bisect_leftfinds the firstTrueposition, which corresponds to the minimum valid speed- Adding 1 adjusts for the 0-based indexing of
bisect_lefton the range starting from 1
Time Complexity: O(n * log(max(piles))) where n is the number of piles
- Binary search runs in
O(log(max(piles)))iterations - Each iteration calls
check, which takesO(n)time to sum over all piles
Space Complexity: O(1) - only using constant extra space for variables
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example: piles = [3, 6, 7, 11], h = 8
Step 1: Initialize binary search boundaries
- Left boundary:
l = 1(minimum eating speed) - Right boundary:
r = max([3, 6, 7, 11]) = 11(maximum useful speed) - Search range:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Step 2: Apply binary search with the check function
The check(k) function calculates if speed k allows finishing within 8 hours:
- For each pile, we calculate hours needed using
(pile + k - 1) // k
Let's evaluate check for different speeds:
k = 1: Hours needed =(3+0)//1 + (6+0)//1 + (7+0)//1 + (11+0)//1 = 3 + 6 + 7 + 11 = 27→27 > 8→Falsek = 2: Hours needed =(3+1)//2 + (6+1)//2 + (7+1)//2 + (11+1)//2 = 2 + 4 + 4 + 6 = 16→16 > 8→Falsek = 3: Hours needed =(3+2)//3 + (6+2)//3 + (7+2)//3 + (11+2)//3 = 1 + 3 + 3 + 5 = 12→12 > 8→Falsek = 4: Hours needed =(3+3)//4 + (6+3)//4 + (7+3)//4 + (11+3)//4 = 1 + 2 + 2 + 3 = 8→8 ≤ 8→Truek = 5: Hours needed =(3+4)//5 + (6+4)//5 + (7+4)//5 + (11+4)//5 = 1 + 2 + 2 + 3 = 8→8 ≤ 8→True
The sequence of check results looks like:
Speed: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] Result: [False, False, False, True, True, True, True, True, True, True, True]
Step 3: Find the minimum valid speed
Using bisect_left:
- We search for the leftmost position where
Truecan be inserted - The first
Trueappears at index 3 (corresponding to speed 4) - Since we're searching in
range(1, 12), we add 1 to convert from 0-based index to actual speed - Result:
1 + 3 = 4
Verification for k = 4:
- Pile 1 (3 bananas): needs
⌈3/4⌉ = 1hour - Pile 2 (6 bananas): needs
⌈6/4⌉ = 2hours - Pile 3 (7 bananas): needs
⌈7/4⌉ = 2hours - Pile 4 (11 bananas): needs
⌈11/4⌉ = 3hours - Total:
1 + 2 + 2 + 3 = 8hours ✓
The minimum eating speed is 4 bananas per hour.
Time and Space Complexity
The time complexity is O(n × log M), where n is the length of the array piles and M is the maximum value in piles. This is because:
- The binary search using
bisect_leftsearches through the range[1, max(piles)], which hasMelements, takingO(log M)iterations - For each iteration of the binary search, the
checkfunction is called, which computes the sum of(x + k - 1) // kfor all elements inpiles, takingO(n)time - Therefore, the total time complexity is
O(n × log M)
The space complexity is O(1). The algorithm only uses a constant amount of extra space for:
- The variable
kin thecheckfunction - The internal variables used by
bisect_left - The generator expression in the
sumfunction doesn't create a list but computes values on-the-fly
Note that while range(1, max(piles) + 1) is passed to bisect_left, the implementation of bisect_left with a key function doesn't materialize the entire range into memory but treats it as virtual bounds for the binary search.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
The most critical mistake is using a different binary search variant that doesn't match the standard template. The template-compliant approach uses first_true_index to track the answer, while left <= right, and right = mid - 1 when feasible.
Wrong approach (alternative variant):
left, right = 1, max(piles)
while left < right:
mid = (left + right) // 2
if feasible(mid):
right = mid # WRONG: should be right = mid - 1
else:
left = mid + 1
return left # WRONG: should track first_true_index
Correct template-compliant approach:
left, right = 1, max(piles)
first_true_index = -1
while left <= right:
mid = (left + right) // 2
if feasible(mid):
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
2. Incorrect Binary Search Boundaries
A common mistake is setting the right boundary as sum(piles) or sum(piles) // h instead of max(piles). While these might seem logical, they're inefficient and can cause issues:
Why it's wrong:
- If
k > max(piles), Koko still takes 1 hour per pile (she can't eat faster than the pile size) - Setting
r = sum(piles)creates an unnecessarily large search space - Using
sum(piles) // hmight be too small if piles are unevenly distributed
Correct approach:
left, right = 1, max(piles)
3. Incorrect Ceiling Division
Many developers incorrectly calculate the hours needed for each pile:
Common mistakes:
# Wrong: Simple division truncates, underestimating hours needed hours = pile // k # This would give 2 for pile=7, k=3 (should be 3) # Wrong: Using math.ceil without importing hours = ceil(pile / k) # NameError if math not imported
Correct approach:
# Efficient ceiling division without imports hours = (pile + k - 1) // k
4. Integer Overflow in Other Languages
While not an issue in Python, developers coming from other languages might worry about overflow:
Safe approach:
mid = left + (right - left) // 2
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!