Skip to content

Intermediate: core algorithms · Sorting and searching · topic 30 of 61 · 40 min

Binary search on the answer

Explanation

Sometimes the list is not what you search; the ANSWER is. If the answer is a number in a known range and a check "is x enough?" is monotonic (false, false, …, true, true), binary search finds the smallest x that works in O(log range) checks. Koko eating bananas, shipping packages within D days, integer square root and allocating book pages are the classic examples.

Searching a range of answers

Some problems ask for the minimum (or maximum) value that satisfies a condition: the slowest eating speed that still finishes in time, the smallest truck capacity that ships everything in 5 days. Trying every value 1, 2, 3, … works but can be slow when the range is large (up to 10⁹). Binary search can jump straight to the answer, as long as the condition behaves nicely.

Monotonic checks

Write a function ok(x) that answers "is x enough?". It is monotonic if, once it becomes true, it stays true for every bigger x: false, false, false, true, true, true. A bigger truck never makes shipping take more days, so "can ship within D days with capacity x" is monotonic. You need the first true: the lower bound from Topic 4, applied to numbers instead of a list.

Think of it like this: Setting an alarm: if waking at 7:00 gets you to college on time, so does 6:30 and 6:00. If 7:30 is too late, so is 8:00. Somewhere there is a latest time that still works, and you can find it by halving the window instead of trying every minute.

The template

python
def first_true(lo, hi, ok):
    """Smallest x in [lo, hi] with ok(x) True; assumes ok(hi) is True."""
    while lo < hi:
        mid = (lo + hi) // 2
        if ok(mid):
            hi = mid          # mid works: the answer is mid or smaller
        else:
            lo = mid + 1      # mid fails: the answer is bigger
    return lo
O(log(hi − lo)) calls to ok. If ok costs O(n), the total is O(n log(range)).

Three decisions make it work: lo is a value that might be the answer (the smallest possible), hi is a value surely good enough, and ok(x) is monotonic. Get these right and the loop is always the same four lines.

Koko eating bananas

There are piles of bananas and h hours. Each hour Koko picks one pile and eats up to k bananas from it (if the pile has fewer, she finishes it and waits for the next hour). Find the smallest speed k that finishes all piles within h hours. A pile of p takes ceil(p / k) hours, which in integers is (p + k − 1) // k. The answer is between 1 and max(piles), because eating max(piles) per hour finishes every pile in one hour each.

python
def min_eating_speed(piles, h):
    def ok(k):
        return sum((p + k - 1) // k for p in piles) <= h
    lo, hi = 1, max(piles)
    while lo < hi:
        mid = (lo + hi) // 2
        if ok(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

print(min_eating_speed([3, 6, 7, 11], 8))   # Output: 4
At speed 4 the hours are 1 + 2 + 2 + 3 = 8. At speed 3 they are 1 + 2 + 3 + 4 = 10, too many.

Ship packages within D days

Packages with weights must be shipped IN ORDER, some each day, and a day's total cannot exceed the ship's capacity. Find the smallest capacity that ships everything within D days. Check a capacity greedily: fill the current day until the next package does not fit, then start a new day; count the days. The answer is at least max(weights) (the heaviest package must fit) and at most sum(weights) (everything in one day).

Integer square root

The integer square root of n is the largest x with x × x ≤ n. Here you want the LAST true, so either search for the first x with x × x > n and subtract 1, or round mid up: mid = (lo + hi + 1) // 2, then lo = mid when it works and hi = mid − 1 when it does not. Rounding up stops the loop from getting stuck when hi = lo + 1. Avoid int(n ** 0.5): floats keep only about 15–16 significant digits, so for n = 10¹⁸ − 1 it returns 1,000,000,000 instead of 999,999,999. Python's math.isqrt(n) is exact.

Allocate pages: a placement classic

Books with given page counts, in order, are split among m students; each student gets a contiguous run of books and every book goes to someone. Minimise the maximum pages any student reads. It is the shipping problem in another costume: "can every student read at most x pages?" is checked greedily and is monotonic, the range is max(pages) to sum(pages), and if there are more students than books the answer is impossible (often −1). Painter's partition and splitting an array into m parts with the smallest largest sum are the same pattern.

Common mistakes

  • A check that is not monotonic, so binary search jumps past the answer. Fix: argue in one sentence why a bigger x can never make ok(x) false.
  • A wrong range: lo = 0 when dividing by k (ZeroDivisionError), or hi too small so the true answer is outside. Fix: lo = smallest possible answer, hi = a value that surely works.
  • Using mid = (lo + hi) // 2 with lo = mid when looking for the LAST true: infinite loop. Fix: round up, mid = (lo + hi + 1) // 2.
  • Computing ceil with floats, math.ceil(p / k), which can be off for huge numbers. Fix: (p + k − 1) // k.
  • Forgetting the cost: it is O(n × log(range)), not O(log n). Fix: say both parts in the interview.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Binary search on the answer · 1/8

Binary search on the answer

This lesson uses binary search on the answer itself, not on a list.

Prefer a video by a teacher? Find free videos on “binary search on answer koko eating bananas capacity to ship packages allocate minimum pages” on YouTube ↗

In real life: Choosing the smallest delivery van

A grocery delivery company must deliver a list of orders, in route order, over the next 3 days. A bigger van costs more to rent. The planner wants the smallest van capacity that still finishes in 3 days. Checking one capacity is a quick greedy pass over the orders, and the answer is monotonic, so binary search finds the minimum capacity in about 30 checks even if capacities range up to a billion units.

python
orders = [40, 25, 60, 10, 35, 50, 20]   # kg, in route order
days = 3

def days_needed(cap):
    used, load = 1, 0
    for w in orders:
        if load + w > cap:
            used, load = used + 1, 0
        load += w
    return used

lo, hi = max(orders), sum(orders)
while lo < hi:
    mid = (lo + hi) // 2
    if days_needed(mid) <= days:
        hi = mid
    else:
        lo = mid + 1
print(lo)
Shows 105: the three days carry [40, 25], [60, 10, 35] and [50, 20]. With capacity 104 a fourth day would be needed.

Practice (0/3 done)

Task 1 · Code · Python

Write int_sqrt(n) for an integer n ≥ 0 that returns the largest integer x with x * x <= n, using binary search on x. Do not use math.isqrt, math.sqrt or ** 0.5 (floats are wrong for big n: int((10**18 - 1) ** 0.5) gives 1000000000). It must be exact for n up to 10**30. Example: int_sqrt(15) returns 3.

Task 2 · Code · Python

piles[i] is the number of bananas in pile i (each ≥ 1), and h ≥ len(piles) is the number of hours. Each hour Koko chooses one pile and eats up to k bananas from it; if the pile has fewer than k, she eats them all and does nothing else that hour. Write min_eating_speed(piles, h) that returns the SMALLEST integer k that lets her finish every pile within h hours. Binary search k between 1 and max(piles); the hours for speed k are the sum of ceil(p / k), computed as (p + k - 1) // k. Example: min_eating_speed([3, 6, 7, 11], 8) returns 4.

Task 3 · Code · Python

weights[i] is the weight of package i (each ≥ 1). Packages must be shipped IN THE GIVEN ORDER over at most days days (days ≥ 1); each day the ship carries a run of the next packages whose total weight is at most its capacity. Write ship_within_days(weights, days) that returns the SMALLEST capacity that ships everything within days days. Binary search the capacity between max(weights) and sum(weights), with a greedy check that counts the days needed. Example: ship_within_days([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5) returns 15.

Quick quiz

Question 1

1.What must be true of ok(x) to binary search the answer?

Question 2

2.For "ship within D days", what is a correct search range for the capacity?

Question 3

3.Koko has piles [9, 4] and speed k = 4. How many hours does she need?

Question 4

4.Searching for the LAST x with x × x ≤ n, why use mid = (lo + hi + 1) // 2?

Question 5

5.ok(x) costs O(n) and the answer lies between 1 and 10⁹. What is the total cost?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What must be true of ok(x) to binary search the answer?

Answer: It must be monotonic: once true, it stays true for bigger x

Binary search relies on a single switch from false to true.

For "ship within D days", what is a correct search range for the capacity?

Answer: max(weights) to sum(weights)

The heaviest package must fit, and a capacity equal to the total ships everything in one day.

Koko has piles [9, 4] and speed k = 4. How many hours does she need?

Answer: 4

The pile of 9 takes ceil(9 / 4) = 3 hours and the pile of 4 takes 1 hour: 4 hours in total.

Searching for the LAST x with x × x ≤ n, why use mid = (lo + hi + 1) // 2?

Answer: With lo = mid on success, rounding down could leave lo unchanged and loop forever

When hi = lo + 1, rounding down gives mid = lo; rounding up makes progress.

ok(x) costs O(n) and the answer lies between 1 and 10⁹. What is the total cost?

Answer: O(n log 10⁹), about 30 × n

About log₂(10⁹) ≈ 30 checks, each O(n).

Why can lo = mid (instead of mid + 1) loop forever in the inclusive template?Related

Answer: When hi = lo + 1, mid equals lo, so the range never shrinks

(lo + hi) // 2 rounds down, so with two items mid = lo, and lo = mid changes nothing.

From Binary search

Why write mid = lo + (hi - lo) / 2 instead of (lo + hi) / 2?Related

Answer: It avoids overflow when lo + hi is larger than the type can hold

lo + hi can overflow for large values; the difference cannot.

From Core DSA patterns in C++

Why is the monotonic stack walk O(n) even with a while loop inside the for loop?Related

Answer: Each index is pushed once and popped at most once over the whole walk

Count the total work: n pushes and at most n pops.

From Monotonic stacks and queues

What must be true for binary search to work?Related

Answer: The array is sorted

Binary search relies on sorted order.

From Two-dimensional arrays and array algorithms

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Sorting and searching”. The module exam comes next.
  2. Module 6 exam: Sorting and searching · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Trees and binary search trees. 61 topics left in the course before the final exam.

← Previous: Binary search