Binary Search on the Answer
Searching for an answer, not in an array
Koko has piles of bananas, [3, 6, 7, 11], and 8 hours. Each hour she picks a pile and eats up to k bananas from it (a smaller pile is finished, and the rest of that hour is wasted). What's the slowest speed k that finishes every pile within 8 hours?
There's no sorted array here. But look at the question "can she finish at speed k?" for k = 1, 2, 3, …. At those speeds she needs 27, 15, 10, 8, 8, 6 hours.
Faster never takes longer, so once a speed works, every faster speed works. The answers are no, no, no, then yes from k = 4 on: they flip once. That's the first yes from Introduction to Binary Search, over the possible answers instead of an array. This is binary search on the answer.

The recipe
Every problem in this lesson is solved in the same five steps:
- What are you choosing? A number: a speed, a capacity, a distance.
- The check. Given a candidate x, can it be done? Write it as its own function. It's usually a greedy pass over the input, O(n).
- Why it flips once. If x works, does every more generous x work too? Faster eating, a bigger ship, a looser limit: yes. This is the step to prove, not assume.
- The bounds.
lois the smallest answer that could possibly work,hione that surely does. Loose bounds are fine: a range of a billion costs only 30 checks. - The loop. The first-yes loop from Introduction to Binary Search for a minimum, its last-yes loop for a maximum.
For Koko: the speed; the check adds up ⌈pile / k⌉ hours; faster never takes longer; lo = 1, hi = max(piles) (at that speed every pile takes one hour, and the problem promises at least that many hours); the first yes.
Binary search over Koko's speeds — The row is the possible speeds. Each step checks the middle speed: green when she finishes in time, amber when she doesn't.
long long hoursAt(const vector<int>& piles, long long k) {
long long hours = 0;
for (int p : piles) hours += (p + k - 1) / k; // ceil(p / k): a pile takes whole hours
return hours;
}
int minEatingSpeed(const vector<int>& piles, int h) {
int lo = 1, hi = *max_element(piles.begin(), piles.end()); // speed max always works: one hour a pile
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (hoursAt(piles, mid) <= h) hi = mid; // yes: mid is fast enough, maybe slower works too
else lo = mid + 1; // no: she needs to eat faster than mid
}
return lo; // the slowest speed that finishes in h hours
}
def hours_at(piles, k):
return sum((p + k - 1) // k for p in piles) # ceil(p / k): a pile takes whole hours
def min_eating_speed(piles, h):
lo, hi = 1, max(piles) # speed max always works: one hour a pile
while lo < hi:
mid = (lo + hi) // 2
if hours_at(piles, mid) <= h:
hi = mid # yes: mid is fast enough, maybe slower works too
else:
lo = mid + 1 # no: she needs to eat faster than mid
return lo # the slowest speed that finishes in h hours
static long hoursAt(int[] piles, long k) {
long hours = 0;
for (int p : piles) hours += (p + k - 1) / k; // ceil(p / k): a pile takes whole hours
return hours;
}
static int minEatingSpeed(int[] piles, int h) {
int lo = 1, hi = Arrays.stream(piles).max().getAsInt(); // speed max always works: one hour a pile
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (hoursAt(piles, mid) <= h) hi = mid; // yes: mid is fast enough, maybe slower works too
else lo = mid + 1; // no: she needs to eat faster than mid
}
return lo; // the slowest speed that finishes in h hours
}
function hoursAt(piles, k) {
let hours = 0;
for (const p of piles) hours += Math.ceil(p / k); // a pile takes whole hours
return hours;
}
function minEatingSpeed(piles, h) {
let lo = 1, hi = Math.max(...piles); // speed max always works: one hour a pile
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (hoursAt(piles, mid) <= h) hi = mid; // yes: mid is fast enough, maybe slower works too
else lo = mid + 1; // no: she needs to eat faster than mid
}
return lo; // the slowest speed that finishes in h hours
}
Each prints 4, 30, 23 and 1 for [3, 6, 7, 11] with h = 8, [30, 11, 23, 4, 20] with h = 5, [30, 11, 23, 4, 20] with h = 6 and [1] with h = 1. Trying every speed would take up to max(piles) checks, 11 here and a billion in the worst case; binary search takes about 30.
Minimize the maximum
Split Array Largest Sum: split [7, 2, 5, 10, 8] into 2 parts that keep their order, so that the largest part's sum is as small as possible. The thing you choose is that cap. The check: with a cap, fill parts greedily from the left and start a new part when the next number won't fit. If that needs k parts or fewer, the cap works. A bigger cap never needs more parts, so the answers flip once.
The bounds: no part can be smaller than the biggest number, and one part holding everything is the total. So lo = max(nums) and hi = sum(nums).
def parts_needed(nums, cap):
parts, current = 1, 0
for x in nums:
if current + x > cap: # x doesn't fit: start a new part with it
parts += 1
current = 0
current += x
return parts
def split_array(nums, k):
lo, hi = max(nums), sum(nums) # no part can be smaller than the biggest number; one part is the total
while lo < hi:
mid = (lo + hi) // 2
if parts_needed(nums, mid) <= k:
hi = mid # yes: a cap of mid fits in k parts, maybe a smaller cap does too
else:
lo = mid + 1 # no: mid is too small
return lo
It returns 18: a cap of 18 needs 2 parts ([7, 2, 5] and [10, 8]), and 17 needs 3. The same check, with different words, solves Capacity To Ship Packages Within D Days (parts are days), Minimize Maximum Pages Allocated (parts are students) and Painter's Partition Problem (parts are painters).

Maximize the minimum
Maximize Minimum Distance (the aggressive cows problem): stalls at [1, 2, 8, 4, 9], 3 cows, and you want the cows as far apart as possible, measured by the closest pair. The thing you choose is that smallest gap. The check: sort the stalls, put a cow in the first, and put the next cow in the first stall at least the gap further on. If all the cows fit, the gap works.
This flips the other way: if a gap works, every smaller gap works too. So the answers run yes, yes, …, no, and the answer is the last yes: the other loop from Introduction to Binary Search, with mid rounded up.
def can_place(stalls, cows, gap):
placed, last = 1, stalls[0] # the first cow goes in the first stall
for s in stalls[1:]:
if s - last >= gap: # far enough from the last cow: place one here
placed += 1
last = s
return placed >= cows
def max_min_distance(stalls, cows):
stalls = sorted(stalls)
lo, hi = 1, stalls[-1] - stalls[0] # the answer is in [lo, hi]
while lo < hi:
mid = (lo + hi + 1) // 2 # round up: the next line may set lo = mid
if can_place(stalls, cows, mid):
lo = mid # yes: a gap of mid works, maybe a bigger one does
else:
hi = mid - 1 # no: mid is too big
return lo
It returns 3: stalls 1, 4 and 8 (or 1, 4, 9) keep every cow at least 3 apart.
Answers that aren't whole numbers
Minimize Max Distance to Gas Station wants a real number, so there's no mid + 1: the range never becomes a single value. Loop a fixed number of times instead. Each step halves the range, so 100 steps shrink any range far below what a double can even tell apart. And when the answer is a whole number but the question is about a real one, like an integer square root, the last-yes loop does it exactly:
def sqrt_real(x):
lo, hi = 0.0, max(1.0, x) # the square root of x is in [lo, hi]
for _ in range(100): # 100 halvings: far below a double's precision
mid = (lo + hi) / 2
if mid * mid >= x:
hi = mid
else:
lo = mid
return hi
def isqrt(x):
lo, hi = 0, x # the largest r with r * r <= x is in [lo, hi]
while lo < hi:
mid = (lo + hi + 1) // 2 # a last yes: round up
if mid * mid <= x:
lo = mid
else:
hi = mid - 1
return lo
It prints 1.414213562 3 4 1000000000: √2 to nine places, then ⌊√15⌋ = 3, ⌊√16⌋ = 4 and ⌊√10^18⌋ = 10^9, with no floating-point rounding to worry about. Square Root of an Integer and Find nth root of m are this loop.
When to search the answer
Clues:
- "the minimum (or maximum) value such that…", where checking one value is easy but finding it directly isn't.
- "minimize the largest" / "maximize the smallest": almost always this pattern.
- A limit you must stay within: hours, days, parts, a threshold.
Before you start, prove the check flips once. If it doesn't, binary search can skip right over the answer: "split into exactly k parts" is the kind of rule to rewrite as "at most k" first. And keep the check fast: binary search runs it about log(range) times, so an O(n) check gives O(n log range) in all.
Common mistakes
Rounding the hours down. p // k
On [3, 6, 7, 11] with h = 8 it returns 3, not 4. A pile of 11 at speed 3 takes 4 hours, not 3: the last hour still counts. Round up: (p + k - 1) // k.
Starting at 0. lo, hi = 0, max(piles)
On [1] with h = 1 it crashes with ZeroDivisionError: integer division or modulo by zero: the first mid is 0, and the check divides by it. The smallest possible speed is 1.
An upper bound that's too small. hi = max(nums) in Split Array Largest Sum
On [7, 2, 5, 10, 8] with k = 2 it returns 10, a cap that needs 3 parts. The answer, 18, was outside the range from the start, so no loop could find it. hi must be a value that surely works: the total.
Forgetting to sort. stalls = sorted(stalls) missing
On [1, 2, 8, 4, 9] with 3 cows it returns 1, not 3. The greedy placement walks the stalls in order of position; out of order, "far enough from the last cow" compares the wrong pairs.
Check yourself
Quiz
1 / 5Multiple choice
Multiple choice
Multiple choice
Multiple choice
Multiple choice
Practice problems
From easier to harder:
- Square Root of an Integer: the last yes, x · x ≤ n.
- Find nth root of m: the same with a power.
- Kth Missing Positive Number: binary search on how many numbers are missing before an index.
- Find the Smallest Divisor Given a Threshold: Koko with a different sum.
- Koko Eating Bananas: the recipe.
- Minimum Number of Days to Make m Bouquets: the check counts bouquets of adjacent flowers.
- Capacity To Ship Packages Within D Days: minimize the maximum.
- Maximize Minimum Distance: the last yes.
- Minimize Maximum Pages Allocated: minimize the maximum, with students.
- Split Array Largest Sum: the same check.
- Painter's Partition Problem: the same check, with painters.
- Minimize Max Distance to Gas Station: a real-valued answer.
Next: Binary Search on Two Sorted Arrays and Matrices.