Facebook Pixel

875. Koko Eating Bananas

LeetCode ↗

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 k or more bananas, she eats exactly k bananas from it
  • If the pile has fewer than k bananas, 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Binary Search?

This problem maps to Binary Search through a short path in the full flowchart.

Graph?noSorted ormonotonic?yesBinary Search

Hours required is monotonically non-increasing in eating speed, so binary search on speed finds the smallest value that finishes within h hours.

Open in Flowchart

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 x is divisible by k, she needs exactly x / k hours
  • If not, she needs x // k + 1 hours (the ceiling of x / 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.

Pattern Learn more about Binary Search patterns.

Watch it run

Step through it below. Binary search finds the slowest eating speed whose total hours still fit within h.

New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

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 True if total hours ≤ h, otherwise False

3. Apply binary search: The code searches for the first speed where check (named feasible in the code) returns True:

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

This works because:

  • Over the speeds 1, 2, ..., max(piles), the results of feasible look like [False, False, ..., False, True, True, ..., True]
  • When mid is feasible, it is a candidate answer, so the code records it in first_true_index and continues searching the smaller speeds with right = mid - 1
  • When mid is not feasible, every smaller speed is also too slow, so the search moves to left = mid + 1
  • When the loop ends, first_true_index holds the smallest feasible speed. Speed max(piles) is always feasible because h >= len(piles), so the answer is always found

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 takes O(n) time to sum over all piles

Space Complexity: O(1) - only using constant extra space for variables

Example Walkthrough

Prefer text? Read the step-by-step 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

The binary search starts with left = 1, right = 11, first_true_index = -1:

  • mid = 6: hours = 1 + 1 + 2 + 2 = 6 → 6 ≤ 8 → True. Record first_true_index = 6, set right = 5
  • mid = 3: hours = 12 → False. Set left = 4
  • mid = 4: hours = 8 → True. Record first_true_index = 4, set right = 3
  • Now left = 4 > right = 3, so the loop ends and returns 4

Verification for k = 4:

  • Pile 1 (3 bananas): needs ⌈3/4⌉ = 1 hour
  • Pile 2 (6 bananas): needs ⌈6/4⌉ = 2 hours
  • Pile 3 (7 bananas): needs ⌈7/4⌉ = 2 hours
  • Pile 4 (11 bananas): needs ⌈11/4⌉ = 3 hours
  • Total: 1 + 2 + 2 + 3 = 8 hours ✓

The minimum eating speed is 4 bananas per hour.

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
41
1class 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            long 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}
36
1class 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            long long 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};
31
1/**
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}
34

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 runs over the range [1, max(piles)], which has M values, taking O(log M) iterations
  • Each iteration calls feasible, which sums (pile_size + eating_speed - 1) // eating_speed over all piles, taking O(n) time
  • Therefore, the total time complexity is O(n × log M)

The space complexity is O(1). The algorithm only uses a constant number of variables (left, right, mid, first_true_index), and the generator expression inside sum computes values one at a time without building a list.

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Updating the Wrong Boundary After a Feasible Speed

When mid is feasible, it may be the answer itself, so the search must keep it as a candidate. Moving left up instead discards it.

Incorrect approach:

left, right = 1, max(piles)
while left < right:
    mid = (left + right) // 2
    if feasible(mid):
        left = mid + 1  # WRONG: throws away a feasible speed
    else:
        right = mid
return left

For piles = [3, 6, 7, 11] and h = 8, this returns 11 instead of 4.

Correct approach: Record the feasible speed and search to its left:

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

The while left < right form with right = mid on a feasible speed and return left is also correct; the two styles find the same answer.

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) // h might 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 When Summing Hours in Other Languages

Python integers do not overflow, but in Java and C++ the total number of hours can exceed the 32-bit range. With piles = [805306368, 805306368, 805306368] and h = 1000000000, the search checks mid = 1, where the total is 2,415,919,104 hours. A 32-bit int wraps this to a negative number, which passes the <= h test, so the function returns 1 instead of 3.

Safe approach: Accumulate the total in a 64-bit type:

long totalHours = 0;

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More