{x}
blog image

Koko Eating Bananas

Koko loves to eat bananas. There are n piles of bananas, the ith pile has piles[i] bananas. The guards have gone and will come back in h hours.

Koko can decide her bananas-per-hour eating speed of k. Each hour, she chooses some pile of bananas and eats k bananas from that pile. If the pile has less than k bananas, she eats all of them instead and will not eat any more bananas during this hour.

Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return.

Return the minimum integer k such that she can eat all the bananas within h hours.

 

Example 1:

Input: piles = [3,6,7,11], h = 8
Output: 4

Example 2:

Input: piles = [30,11,23,4,20], h = 5
Output: 30

Example 3:

Input: piles = [30,11,23,4,20], h = 6
Output: 23

 

Constraints:

  • 1 <= piles.length <= 104
  • piles.length <= h <= 109
  • 1 <= piles[i] <= 109

Solution Explanation: Koko Eating Bananas

This problem asks to find the minimum eating speed Koko needs to consume all bananas in h hours. A binary search approach is highly efficient due to the problem's inherent monotonicity: if Koko can eat all bananas at speed k, she can also do so at any speed greater than k.

Algorithm:

  1. Binary Search Space: The minimum speed is 1 banana/hour, and the maximum is the largest pile size (since she might need to eat that many in one hour). We initialize left = 1 and right = max(piles).

  2. Binary Search Iteration: We repeatedly halve the search space until left and right converge.

  3. Midpoint Calculation: mid = (left + right) // 2 (integer division). This represents the current test speed.

  4. Time Calculation: We iterate through piles, calculating the time needed to eat each pile at speed mid. We use (pile + mid - 1) // mid to handle cases where a pile doesn't divide perfectly by mid (ceiling division). The total time total_time is the sum of these individual times.

  5. Check Condition: If total_time <= h, it means Koko can finish eating within the time limit at this speed. We update right = mid to search for a potentially lower speed. Otherwise, if total_time > h, we need a faster speed, so we update left = mid + 1.

  6. Result: The loop continues until left == right, at which point left (or right) holds the minimum speed that satisfies the condition.

Time Complexity Analysis:

  • The binary search takes O(log M) iterations, where M is the maximum value in piles.
  • In each iteration, we iterate through piles once, which takes O(n) time, where n is the number of piles.
  • Therefore, the overall time complexity is O(n log M).

Space Complexity Analysis:

The algorithm uses only a few variables to store the boundaries, mid-point, and total time. The space complexity is O(1), constant.

Code Examples (Python):

import bisect
 
class Solution:
    def minEatingSpeed(self, piles: list[int], h: int) -> int:
        def check(k: int) -> bool:
            return sum((x + k - 1) // k for x in piles) <= h  # Efficient ceiling division
 
        # Use bisect_left for a more concise binary search
        return 1 + bisect_left(range(1, max(piles) + 1), True, key=check)
 
 
#Alternative implementation without bisect:
 
class Solution:
    def minEatingSpeed(self, piles: list[int], h: int) -> int:
        left, right = 1, max(piles)
        while left < right:
            mid = (left + right) // 2
            total_time = sum((p + mid - 1) // mid for p in piles)  # Ceiling division
            if total_time <= h:
                right = mid  # Try a lower speed
            else:
                left = mid + 1  # Need a higher speed
        return left  # left == right at this point
 

The bisect_left approach is more concise but might be slightly less readable for those unfamiliar with the bisect module. Both achieve the same time and space complexity. Other language examples would follow similar logic, adapting the syntax and potentially using built-in functions equivalent to bisect_left.