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 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 where- Truecan 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 first- Trueposition, 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→- False
- k = 2: Hours needed =- (3+1)//2 + (6+1)//2 + (7+1)//2 + (11+1)//2 = 2 + 4 + 4 + 6 = 16→- 16 > 8→- False
- k = 3: Hours needed =- (3+2)//3 + (6+2)//3 + (7+2)//3 + (11+2)//3 = 1 + 3 + 3 + 5 = 12→- 12 > 8→- False
- k = 4: Hours needed =- (3+3)//4 + (6+3)//4 + (7+3)//4 + (11+3)//4 = 1 + 2 + 2 + 3 = 8→- 8 ≤ 8→- True
- k = 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.
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4class Solution:
5    def minEatingSpeed(self, piles: List[int], h: int) -> int:
6        """
7        Find the minimum eating speed to consume all banana piles within h hours.
8      
9        Args:
10            piles: List of integers representing the number of bananas in each pile
11            h: Maximum hours available to eat all bananas
12          
13        Returns:
14            Minimum integer eating speed (bananas per hour) to finish all piles
15        """
16      
17        def can_finish_in_time(eating_speed: int) -> bool:
18            """
19            Check if all piles can be consumed within h hours at given eating speed.
20          
21            Args:
22                eating_speed: Number of bananas that can be eaten per hour
23              
24            Returns:
25                True if all piles can be finished within h hours, False otherwise
26            """
27            # Calculate total hours needed to eat all piles at current speed
28            # Using ceiling division: (x + k - 1) // k is equivalent to math.ceil(x / k)
29            total_hours = sum((pile_size + eating_speed - 1) // eating_speed 
30                            for pile_size in piles)
31            return total_hours <= h
32      
33        # Binary search for the minimum valid eating speed
34        # Search range: from 1 to max(piles) bananas per hour
35        # bisect_left finds the leftmost position where can_finish_in_time returns True
36        min_speed = 1 + bisect_left(range(1, max(piles) + 1), True, 
37                                    key=can_finish_in_time)
38      
39        return min_speed
401class Solution {
2    public int minEatingSpeed(int[] piles, int h) {
3        // Binary search bounds: minimum speed is 1, maximum is 10^9
4        int left = 1;
5        int right = (int) 1e9;
6      
7        // Binary search to find the minimum eating speed
8        while (left < right) {
9            // Calculate middle speed
10            int mid = (left + right) >> 1;  // Equivalent to (left + right) / 2
11          
12            // Calculate total hours needed at current speed
13            int totalHours = 0;
14            for (int pile : piles) {
15                // Calculate hours to eat current pile (ceiling division)
16                // (pile + mid - 1) / mid is equivalent to Math.ceil(pile / mid)
17                totalHours += (pile + mid - 1) / mid;
18            }
19          
20            // Check if current speed is valid
21            if (totalHours <= h) {
22                // Current speed works, try to find a smaller speed
23                right = mid;
24            } else {
25                // Current speed is too slow, need to increase speed
26                left = mid + 1;
27            }
28        }
29      
30        // Return the minimum valid eating speed
31        return left;
32    }
33}
341class Solution {
2public:
3    int minEatingSpeed(vector<int>& piles, int h) {
4        // Binary search range: minimum speed is 1, maximum is the largest pile
5        int left = 1;
6        int right = ranges::max(piles);
7      
8        // Binary search to find the minimum eating speed
9        while (left < right) {
10            // Calculate middle speed (using bit shift for division by 2)
11            int mid = (left + right) >> 1;
12          
13            // Calculate total hours needed at current speed
14            int totalHours = 0;
15            for (int pile : piles) {
16                // Ceiling division: (pile + mid - 1) / mid equals ceil(pile / mid)
17                // This calculates hours needed to eat current pile at speed 'mid'
18                totalHours += (pile + mid - 1) / mid;
19            }
20          
21            // If we can finish within h hours, try slower speed (smaller k)
22            if (totalHours <= h) {
23                right = mid;  // mid could be the answer, keep it in range
24            } else {
25                // If we can't finish in time, need faster speed
26                left = mid + 1;  // mid is too slow, exclude it
27            }
28        }
29      
30        // left == right, this is the minimum eating speed
31        return left;
32    }
33};
341/**
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    // Initialize binary search boundaries
10    // Minimum speed is 1, maximum speed is the largest pile size
11    let left: number = 1;
12    let right: number = Math.max(...piles);
13  
14    // Binary search for the minimum valid speed
15    while (left < right) {
16        // Calculate middle point using bit shift for integer division
17        const middle: number = (left + right) >> 1;
18      
19        // Calculate total hours needed at current speed
20        // For each pile, calculate hours needed (rounded up)
21        const totalHours: number = piles
22            .map((pileSize: number) => Math.ceil(pileSize / middle))
23            .reduce((accumulator: number, current: number) => accumulator + current, 0);
24      
25        // Check if current speed is valid
26        if (totalHours <= h) {
27            // Current speed works, try to find a smaller valid speed
28            right = middle;
29        } else {
30            // Current speed is too slow, need to increase speed
31            left = middle + 1;
32        }
33    }
34  
35    // Return the minimum valid eating speed
36    return left;
37}
38Time 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. 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:
# Right boundary should be max(piles)
left, right = 1, max(piles)
2. 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
# Wrong: Float division issues
hours = int(pile / k) + (1 if pile % k > 0 else 0)  # Verbose and error-prone
Correct approach:
# Efficient ceiling division without imports hours = (pile + k - 1) // k # Or alternatively: hours = -(-pile // k) # Using negative division trick
3. Off-by-One Error in Binary Search
When implementing manual binary search, it's easy to get the boundaries wrong:
Common mistake:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
    left, right = 1, max(piles)
  
    while left < right:  # Wrong: might miss the answer
        mid = (left + right) // 2
        if can_finish(mid):
            right = mid - 1  # Wrong: might skip the minimum valid speed
        else:
            left = mid + 1
  
    return left  # Might return invalid speed
Correct approach:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
    left, right = 1, max(piles)
  
    while left < right:
        mid = (left + right) // 2
        if can_finish(mid):
            right = mid  # Keep mid as potential answer
        else:
            left = mid + 1
  
    return left
4. Integer Overflow in Other Languages
While not an issue in Python, developers coming from other languages might worry about overflow:
Potential issue in languages like Java/C++:
# This could overflow in other languages mid = (left + right) // 2
Safe approach (habit from other languages):
mid = left + (right - left) // 2
5. Misunderstanding the bisect_left Usage
The clever use of bisect_left with a boolean key can be confusing:
Common confusion:
# Wrong: Forgetting to add 1 to the result
return bisect_left(range(1, max(piles) + 1), True, key=check)  # Off by 1
# Wrong: Using bisect_right instead
return bisect_right(range(1, max(piles) + 1), True, key=check)  # Wrong boundary
Why the +1 is needed:
- bisect_leftreturns the index in the range where- Truewould be inserted
- Since our range starts at 1 (not 0), we need to add 1 to get the actual speed value
- The range [1, 2, 3, ..., max(piles)]has indices[0, 1, 2, ..., max(piles)-1]
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___ in the given code snippet.
1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
121public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
161function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!