DSA sheet · Binary Search · Binary search on answer

Koko Eating Bananas

This is the first problem of a new pattern called binary search on answer. Until now we ran binary search on the array we were given. Here the array is not where we search. We search over the possible answers (all the speeds Koko could eat at), a range that we decide ourselves. The teacher first solves it the slow way (try every speed from 1 upward), shows why it gets TLE, and then turns the same idea into binary search. Learn this one properly: problems 15 to 22 all follow the same recipe.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Why binary search works at all

Binary search does not really need a "sorted array". What it needs is a yes/no question whose answers line up like this: all the "no"s on one side, all the "yes"s on the other side, with one switch point in the middle. Then one look at the middle tells us which half can't contain the switch point, and we throw that half away. A sorted array is just one case of this: "is arr[i] >= target?" is no, no, no, yes, yes.

A yes/no question with this "no…no then yes…yes" shape is called monotonic (it changes direction only once, it never flips back).

low, high, mid

Why mid = low + (high - low) // 2 and not (low + high) // 2?

Both give the same number. The teacher always writes the first form because in Java/C++ a normal int stops at about 2.1 × 10⁹. If low and high are both near 10⁹, then low + high goes past that limit and overflows (wraps around to a garbage negative number). high - low is never bigger than high, so the first form is always safe.

In Python, integers never overflow, so both forms work. We still write the safe form so the habit carries over to interviews in any language.

The new pattern: binary search on answer

In some problems the question asks for the smallest (or largest) number that makes something possible: the minimum speed, the minimum capacity, the minimum number of days. For those:

  1. The answer range. Decide the smallest answer that could ever make sense (low) and the biggest one we'd ever need (high). The answer must be somewhere in [low, high]. This range is a list of whole numbers, so it is already sorted, even when the input array is not.
  2. The check function. Write check(x): "if the answer were x, would it work?" It returns True or False.
  3. Monotonic. If x works, then every bigger x also works (eating faster never makes you slower). So over the range, check gives no no no yes yes yes. One switch point.
  4. Which side to keep. We want the first yes (the minimum). If check(mid) is yes, mid might be the answer, but a smaller one might also work, so save mid and go left. If it's no, mid and everything below it is useless, so go right.
answer range:  low ......................................... high
check(x):       no   no   no   no  YES  YES  YES  YES  YES
                                   ^
                          the first YES = the minimum answer
The whole pattern in 2 jobsThe teacher sums it up like this: binary search on answer is about (1) choosing low and high and (2) writing the check function. For a minimum: yes → go left. For a maximum: yes → go right.

Ceiling division (we need it here)

Ceiling of a number means "round it up to the next whole number": ceil(1.5) = 2, ceil(1.1) = 2, ceil(3.0) = 3. Normal integer division p // k rounds down (gives the floor). The trick to round up using only integers is (p + k - 1) // k. Adding k - 1 pushes any leftover over to the next whole number, but it can't push an exact division over.

p, kp / kp // k (floor)(p + k − 1) // k (ceil)
3, 21.512
6, 23.033
11, 42.7523

Part A · Brute force: try every speed from 1 upward

LeetCode 875

1The question in simple words

There are n piles of bananas. Pile i has piles[i] bananas. The guards will be back in h hours. Koko picks an eating speed k = bananas per hour. Find the smallest speed k that lets her finish all the piles within h hours.

One special rule: each hour she sits at one pile. If that pile has fewer than k bananas left, she finishes it and wastes the rest of that hour. She can't move to the next pile in the same hour.

index0123
piles36711h = 8 → answer 4

The inputs are numbers of bananas and time (h). The output is a speed. So the thing we return is not even an element of the array. That's the first hint that this is "binary search on answer".

2What the constraints tell us

3Intuition: how would you do it by hand?

Forget binary search for a moment. If someone asked you for the minimum speed, you would simply try speeds one by one, starting from the slowest: "speed 1, does she finish in time? No. Speed 2? No. Speed 3? No. Speed 4? Yes!" The first speed that works is the smallest one, because we tried them in increasing order.

Notice what we are looping over: not the array, but a range of speeds we chose ourselves. That's the key idea of the whole pattern.

4Building the logic from examples

How many hours does one speed take?

For each pile, the hours needed = bananas ÷ speed, rounded up (because a half-used hour is still a full hour gone). Add this up over all piles. The teacher works it out for speeds 1, 2, 3 and 4 with piles = [3, 6, 7, 11], h = 8:

speed kpile 3pile 6pile 7pile 11total hours<= 8 ?
13671127no, too slow
21.5 → 233.5 → 45.5 → 615no
3122.33 → 33.67 → 410no
40.75 → 11.5 → 21.75 → 22.75 → 38yes → answer 4

Each time the total was more than 8, we had to speed up: a higher speed means fewer hours. At speed 4 the total is exactly 8.

Doubt: "within h hours": does it have to be exactly 8?
→ No. "Within" means 8 or less. Here speed 4 happens to give exactly 8, but for some other input the first working speed might finish in 7 hours. That's still fine, so the test is hours <= h, not ==.
Doubt: why do we round up? 3 bananas at speed 2 is 1.5 hours.
→ Because of the rule in the question: if the pile has fewer than k bananas left, she eats them and then waits out the rest of that hour. After 1.5 hours she is not allowed to start the next pile; she has to wait until hour 2. So every pile costs a whole number of hours: the ceiling. In code: (p + k - 1) // k.

Where should the speeds start and stop? (low and high)

The teacher spends real time on this, because choosing low and high well is exactly what the interviewer wants to see.

Also, since 11 already works and we want the minimum, we would only ever look left of 11 (10, 9, 8…), never right. So max(piles) is a safe ceiling.

Doubt: could we just search from −∞ to +∞?
→ It would still find the answer, but why search a huge range when we already know the answer lies between 1 and max(piles)? A smaller range means fewer steps. (With binary search it means a smaller log; with brute force it means far fewer tries.)
Doubt: the teacher says low could also be min(piles) instead of 1. Is that right?
→ It happens to work for this example (min is 3, answer is 4), but it is not safe in general. Take piles = [10, 10], h = 20: speed 1 already works (10 + 10 = 20 hours), so the answer is 1, which is below min(piles) = 10. Starting at 10 would wrongly return 10. Keep low = 1. (A safe tighter low is ceil(sum(piles) / h), because she can eat at most k bananas per hour, but 1 is simple and costs only a step or two more.)
Doubt: for brute force, do we even need high?
→ Not really. Brute force starts at 1 and stops at the first speed that works. Because an answer always exists, a plain while True loop would stop by itself. But binary search needs both ends, so we fix high now and reuse it in Part B.

5Approach steps

  1. Set low = 1, high = max(piles).
  2. For each speed k from low to high:
  3. Add up the hours: for each pile p, add (p + k - 1) // k.
  4. If the total is <= h, return k. It's the first that works, so it's the smallest.

6Code (Python)

Brute force (correct, but TLE on big inputs)
class Solution:
    def minEatingSpeed(self, piles, h):
        low = 1
        high = max(piles)
        for k in range(low, high + 1):        # k = the speed we try
            hours = 0
            for p in piles:
                hours += (p + k - 1) // k     # ceil(p / k)
            if hours <= h:                    # finished in time
                return k                      # first one = smallest
        return high                           # never reached: high always works

7Code line by line

linewhat it means
low = 1 high = max(piles)The answer range: the slowest sensible speed and the speed after which nothing improves.
for k in range(low, high + 1):Try each speed in increasing order. high + 1 because Python's range leaves out the end.
hours = 0Start a fresh count for this speed.
hours += (p + k - 1) // kHours for this pile, rounded up.
if hours <= h: return kShe finishes in time. Since we go upward, this is the first (smallest) speed that works.
return highOnly a safety line. At speed max(piles) each pile takes 1 hour, n hours in total, and h ≥ n, so the loop always returns before this.

8Dry run

piles = [3, 6, 7, 11], h = 8, so low = 1, high = 11.

  1. k = 1 → 3 + 6 + 7 + 11 = 27 hours → 27 > 8 no.
  2. k = 2 → 2 + 3 + 4 + 6 = 15 → no.
  3. k = 3 → 1 + 2 + 3 + 4 = 10 → no.
  4. k = 4 → 1 + 2 + 2 + 3 = 8 → 8 <= 8 yes → return 4 ✓
speed k :   1    2    3    4    5    6    7    8    9   10   11
hours   :  27   15   10    8    8    6    5    5    5    5    4
<= 8 ?  :  no   no   no  YES  YES  YES  YES  YES  YES  YES  YES
                          ^ the first YES = 4

Look at the "hours" row: it only goes down (or stays) as speed goes up. That is why the yes/no row switches only once. Keep this picture in mind for Part B.

9Complexity & remember

Remember the brute forceRange = 1 … max(piles). For each speed, hours = Σ ceil(p / k). The first speed with hours ≤ h is the answer. Correct, but too slow.

Part B · Optimal: binary search on the speed

1The question

Same question as Part A. We keep the same range and the same "hours" calculation. We only change how we walk through the range.

2Constraints

The same. The one that matters now: the range 1 … max(piles) can be 10⁹ long. Walking it one by one is the problem. Halving it is the fix: log₂(10⁹) ≈ 30 steps.

3Intuition: the range of speeds is already sorted

In Part A the outer loop goes 1, 2, 3, … max. Those speeds sit on a number line in sorted order, and the "can she finish?" answer is no…no then yes…yes along that line. So instead of checking every speed in turn, pick the middle speed, check it, and throw away half the range. The inner loop (adding up the hours) stays the same, because we really do have to look at every pile to know the total time.

To keep the code clean, the teacher moves the hours calculation into its own function, canEat(piles, h, k), which returns True if speed k is fast enough.

4Building the decisions from examples

Check mid. If it works → save it and go left

The teacher takes the number line 1 … 11 and checks a speed near the middle, 5: hours = 1 + 2 + 2 + 3 = 8 → 8 ≤ 8, it works. Is 5 the answer? Maybe. We don't know yet whether some speed on the left (smaller) also works. So:

Doubt: she mentions two ways to keep mid. What are they?
→ Way 1: move high = mid (mid stays inside the range, so it can't be lost). This goes with while low < high, and at the end low is the answer.
Way 2 (the one she uses): save ans = mid, then move high = mid - 1. Mid leaves the range, but we have it written down. If later checks find a smaller working speed, ans gets overwritten with it.
Her advice: if Way 1 confuses you, use Way 2, because it's harder to get wrong. Both are shown in the code below.

Check mid. If it doesn't work → go right

Suppose speed 5 had needed 10 hours. Then 5 is not a valid answer, so don't save it. Every speed below 5 is even slower, so those fail too. The answer must be bigger: low = mid + 1.

What to start ans with

Start it as high (= max(piles)). The constraint h ≥ n means max(piles) always works (every pile takes 1 hour, n hours ≤ h). Her extreme example: one pile of 11 bananas and h = 1. The only speed that works is 11 = max(piles). So in the worst case max(piles) is the answer, and it's a safe starting value. (Any starting value is fine really, since the loop will overwrite it.)

5Approach steps

  1. low = 1, high = max(piles), ans = high.
  2. While low <= high: mid = low + (high - low) // 2 (mid is the speed to test).
  3. If canEat(mid): ans = mid, then high = mid - 1 (look for a smaller speed).
  4. Else: low = mid + 1 (need a faster speed).
  5. Return ans.

6Code (Python)

Binary search on answer (Way 2: save ans)
class Solution:
    def minEatingSpeed(self, piles, h):
        low = 1
        high = max(piles)
        ans = high                         # max(piles) always works
        while low <= high:
            mid = low + (high - low) // 2  # mid = the speed k we test
            if self.canEat(piles, h, mid):
                ans = mid                  # works: remember it
                high = mid - 1             # try smaller speeds
            else:
                low = mid + 1              # too slow: try faster
        return ans

    def canEat(self, piles, h, k):
        hours = 0
        for p in piles:
            hours += (p + k - 1) // k      # ceil(p / k)
        return hours <= h
Same idea, Way 1: keep mid inside the range
class Solution:
    def minEatingSpeed(self, piles, h):
        low, high = 1, max(piles)
        while low < high:                  # stop when one speed is left
            mid = low + (high - low) // 2
            hours = sum((p + mid - 1) // mid for p in piles)
            if hours <= h:
                high = mid                 # mid works, keep it in range
            else:
                low = mid + 1
        return low                         # low == high == the answer

7Code line by line

linewhat it means
low = 1 high = max(piles)The answer range, exactly as in brute force.
ans = highA speed we know works. It gets replaced by smaller working speeds.
while low <= high:Keep going while some speed is still untested. <= so the last single speed is tested too.
mid = low + (high - low) // 2The middle speed. Safe form (no overflow in Java/C++; Python doesn't care).
if self.canEat(piles, h, mid):Ask the yes/no question for this speed.
ans = mid high = mid - 1Yes: mid could be the answer, write it down. Then throw away mid and everything to its right; we want something smaller.
low = mid + 1No: mid and everything slower fails. Throw away the left part.
return ansThe smallest speed that ever said yes.
hours += (p + k - 1) // kInside canEat: whole hours for this pile (rounded up).
return hours <= hTrue if she finishes within h hours.

8Dry run (hand table)

piles = [3, 6, 7, 11], h = 8. Range 1 … 11, ans = 11.

steplowhighmidhours at midcanEat?decisionthrown away
111161+1+2+2 = 6yesans = 6, high = 56 … 11
21531+2+3+4 = 10nolow = 41 … 3
34541+2+2+3 = 8yesans = 4, high = 34 … 5
end43low > high → stop → return ans = 4 ✓

The range of speeds shrinking (yellow = mid, grey = thrown away):

speed1234567891011
step 112345678910116 works → keep left
step 212345678910113 fails → keep right
step 312345678910114 works → ans = 4, keep left (nothing left)

Only 3 checks instead of 4 in brute force here, but for a range of 10⁹ it's about 30 checks instead of up to a billion.

9Complexity & remember

Remember KokoSearch the speed, not the array. Range 1 … max(piles). check = Σ ceil(p/k) ≤ h. Works → ans = mid, go left. Fails → go right.

Part C · Revision page

Brute forceBinary search on answer
range of speeds1 … max(piles)1 … max(piles)
how we walk the rangeone by one, upwardcheck the middle, drop half
check for one speedΣ ceil(p / k) ≤ hsame, inside canEat
check worksreturn k right awayans = mid, high = mid − 1
check failstry k + 1low = mid + 1
timeO(max × n) ≈ 10¹³ → TLEO(n log max) ≈ 3 × 10⁵ ✓
spaceO(1)O(1)
binary search on answer, stepfor Koko
what is the answer?a speed k (bananas / hour)
low1 (slowest real speed)
highmax(piles) (faster than this changes nothing)
check(x)hours at speed x ≤ h
why monotonicfaster speed → never more hours
min or max?min → on yes go left
If you remember only 5 lines 1. Binary search on answer = search over possible answers, not over the array.
2. Choose low = 1 and high = max(piles): beyond the biggest pile, it's 1 hour per pile anyway.
3. Hours for a speed = Σ ceil(p / k) = Σ (p + k − 1) // k.
4. Works → save ans, go left. Fails → go right.
5. O(n log max) instead of O(n × max).
Mistakes to avoid ✗ using p // k (floor) instead of the ceiling
✗ testing hours == h instead of hours <= h
✗ starting low at min(piles) (fails for piles = [10, 10], h = 20)
✗ setting high = mid - 1 without saving mid first
✗ in Java/C++: int overflow in the hours sum or in p + k − 1
test it yourself (paste under the Part B solution)
s = Solution()
print(s.minEatingSpeed([3, 6, 7, 11], 8))          # 4
print(s.minEatingSpeed([30, 11, 23, 4, 20], 5))    # 30
print(s.minEatingSpeed([30, 11, 23, 4, 20], 6))    # 23
print(s.minEatingSpeed([10, 10], 20))              # 1  (below min(piles)!)
print(s.minEatingSpeed([11], 1))                   # 11 (worst case = max)
print(s.minEatingSpeed([10**9], 2))                # 500000000

Based on this video: Koko Eating Bananas | Binary Search on Answer