Start learning free

LESSON 331 OF 536 · BINARY SEARCH

Koko Eating Bananas in Java: Binary Search on the Answer

Practice O(n log m) Read the full explanation

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

Koko chooses an eating speed of k bananas per hour. Each hour, she picks one pile and eats k bananas from it. If the pile has fewer than k bananas, she eats all of them and eats nothing more during that hour. So a pile of p bananas takes her p / k hours, rounded up.

Given an array of integers piles and an integer h, return the smallest integer speed k that lets Koko eat all the bananas within h hours.

 

Example 1:

Input: piles = [3,6,7,11], h = 8
Output: 4
Explanation: At speed 4 the piles take 1 + 2 + 2 + 3 = 8 hours, just in time. At speed 3 they would take 1 + 2 + 3 + 4 = 10 hours, too many.

Example 2:

Input: piles = [30,11,23,4,20], h = 5
Output: 30
Explanation: There are as many hours as piles, so every pile must be eaten in one hour, and the biggest pile has 30 bananas.

Example 3:

Input: piles = [30,11,23,4,20], h = 6
Output: 23
Explanation: At speed 23 the piles take 2 + 1 + 1 + 1 + 1 = 6 hours. At speed 22 the pile of 23 takes 2 hours too, so they take 2 + 1 + 2 + 1 + 1 = 7 hours.

 

Constraints:

  • 1 <= n <= 104, where n is the number of piles
  • n <= h <= 109
  • 1 <= piles[i] <= 109

Note:

Your algorithm should run in O(n log m) time, where m is the biggest pile, and use O(1) extra space.

At a slow speed the hours add up fast: 104 piles of 109 bananas take 1013 hours at speed 1, far more than a 32-bit integer can hold.

STEP 1 OF 15
    Ctrl + Enter to check

    JAVA · BINARY SEARCH

    Koko Eating Bananas in Java, explained

    Problem overview

    Koko has several piles of bananas, piles[i] in pile i, and the guards return in h hours. She picks a speed k, in bananas per hour. Each hour she chooses one pile and eats up to k bananas from it. If the pile has fewer than k, she finishes it and wastes the rest of that hour. A pile of p bananas therefore takes p / k hours, rounded up. The method minEatingSpeed returns the smallest integer speed that finishes everything within h hours.

    With piles = [3, 6, 7, 11] and h = 8, the answer is 4. At speed 4 the piles take 1 + 2 + 2 + 3 = 8 hours. At speed 3 they take 1 + 2 + 3 + 4 = 10 hours, which is too many. With piles = [30, 11, 23, 4, 20] and h = 5 there are as many hours as piles, so each pile must go in one hour and the speed is the biggest pile, 30. With h = 6 the answer drops to 23.

    The statement asks for O(n log m) time, where m is the biggest pile, and O(1) extra space.

    Approach: binary search on the speed

    The simple idea is to try speeds 1, 2, 3 and so on until one works. Piles can hold up to a billion bananas, so that can mean a billion attempts, each looking at every pile.

    The way out is to notice a pattern in the answers. If Koko finishes in time at speed k, she also finishes at any faster speed. If she doesn't finish at k, no slower speed works either. For the first example, the total hours at each speed are:

    k=1 hours=27
    k=2 hours=15
    k=3 hours=10
    k=4 hours=8
    k=5 hours=8
    k=6 hours=6
    k=7 hours=5
    k=8 hours=5
    k=9 hours=5
    k=10 hours=5
    k=11 hours=4
    

    The hours never go up as the speed rises, so the speeds split into "too slow" followed by "fast enough", and the answer is the first speed of the second group (4 here, the first speed with 8 hours or fewer). A sorted list with a single switch point is exactly what binary search finds, even though no list of speeds is stored. This technique is called binary search on the answer.

    The search range runs from 1 to the biggest pile. The biggest pile always works: at that speed every pile takes one hour, and the constraints guarantee n <= h, so there are never more piles than hours. No answer is ever bigger, and none is smaller than 1. Each check, computing the total hours for one speed, costs O(n), and the search needs about log2(m) checks, which gives O(n log m).

    Step by step

    1. Set the range. left is 1, the slowest speed, and right starts at piles[0]. The next two steps raise it to the biggest pile.
    2. Loop over the piles with a for-each loop and set right to Math.max(right, pile). After the loop, right is the biggest pile.
    3. Store a safe answer. answer starts at right, a speed known to work, so there is a valid result before the search starts.
    4. Loop while left <= right. When both are equal, one speed is still untried.
    5. Pick a speed halfway: k = left + (right - left) / 2.
    6. Test it with a helper, hours(piles, k), which returns the total hours as a long. You write it after this method.
    7. If the hours are at most h, speed k works. Save it in answer first, because it may be the smallest, then set right = k - 1 to look for a slower speed.
    8. Otherwise k is too slow, and so is everything below it, so set left = k + 1.
    9. Return answer. When the loop ends, every speed below it was too slow.
    10. Write hours. It declares a long total starting at 0, loops over the piles and adds the hours of each pile. It returns total.
    11. Round up with integers only. The hours for a pile are (pile + k - 1) / k. For a pile of 7 at speed 3, 7 / 3 is 2 and forgets the last hour, while (7 + 2) / 3 is 3. For an exact multiple such as 6, (6 + 2) / 3 is still 2, because adding k - 1 never reaches the next multiple.

    The solution in Java

    class Solution {
        public int minEatingSpeed(int[] piles, int h) {
            int left = 1, right = piles[0];
            for (int pile : piles) {
                right = Math.max(right, pile);
            }
            int answer = right;
            while (left <= right) {
                int k = left + (right - left) / 2;
                if (hours(piles, k) <= h) {
                    answer = k;
                    right = k - 1;
                } else {
                    left = k + 1;
                }
            }
            return answer;
        }
    
        long hours(int[] piles, int k) {
            long total = 0;
            for (int pile : piles) {
                total += (pile + k - 1) / k;
            }
            return total;
        }
    }
    

    Details worth knowing:

    • Why hours returns a long. At speed 1, 10,000 piles of 1,000,000,000 bananas take 10,000,000,000,000 hours, far beyond the 32-bit int maximum of 2,147,483,647. Adding those piles into an int total wraps silently: the same sum, computed with an int total, ends as 1316134912, while a long total gives 10000000000000. A wrapped total could even come out smaller than h and make a hopeless speed look fine.
    • The sum pile + k - 1 stays an int. That is safe here: both values are at most 1,000,000,000, so the sum is below 2,147,483,647. Only the running total needs a long. Adding an int to a long with += widens it automatically.
    • A helper without public. hours belongs to the class and is used only by minEatingSpeed, so it needs no modifier.
    • Comparing long with int. In hours(piles, k) <= h, Java widens h to long before comparing, so no cast is needed.

    Complexity

    Time: O(n log m). Finding the biggest pile takes O(n). The search then runs about log2(m) times, around 30 for a billion, and each pass calls hours, which loops over the n piles.

    Space: O(1). The method keeps a handful of numbers.

    Edge cases and common mistakes

    • One pile. [312884470] with h = 312884469 gives 2, and [1000000000] with h = 2 gives 500000000. With h = 1, a pile of 5 needs speed 5.
    • As many hours as piles. The answer is the biggest pile. With [1, 1, 1] and h = 3 it is 1.
    • Forgetting to round up. Plain pile / k drops the last, partial hour, so the method reports speeds that are too slow.
    • An int total. It overflows on large inputs, as shown above. With 10,000 piles of 1,000,000,000 and h = 10000, the correct answer is 1000000000.
    • Not saving k before moving right. The search never comes back to k, so the value would be lost.
    • Starting left at 0. Speed 0 would divide by zero in hours.

    Testing

    • piles = [3, 6, 7, 11], h = 8 gives 4.
    • piles = [30, 11, 23, 4, 20], h = 5 gives 30.
    • piles = [30, 11, 23, 4, 20], h = 6 gives 23.
    • piles = [312884470], h = 312884469 gives 2.
    • piles = [5], h = 1 gives 5.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free