Intermediate: core algorithms · Sorting and searching · topic 30 of 61 · 40 min
Binary search on the answer
Explanation
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.
The template
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 loThree 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.
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: 4Ship 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
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.
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)Practice (0/3 done)
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.
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.
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Sorting in real code: keys, comparators, counting sortSorting and searching · same module
- Merge sort and quick sortSorting and searching · same module
- Core DSA patterns in C++C++ Programming (modern C++ and DSA) · Competitive programming, interviews and careers
- Two-dimensional arrays and array algorithmsJava Programming (Core to Spring Boot) · Arrays and strings
- 2D arrays and classic array algorithmsC Programming (from zero to systems) · Functions and arrays
- Binary search treesTrees and binary search trees
Questions people ask about this
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.
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.
What must be true for binary search to work?Related
Answer: The array is sorted
Binary search relies on sorted order.
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
- This is the last topic of “Sorting and searching”. The module exam comes next.
- Module 6 exam: Sorting and searching · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Trees and binary search trees. 61 topics left in the course before the final exam.