Binary search on answer finds an optimal numeric value by searching over possible answers instead of searching over array indices. It is used when a direct formula is difficult, but a candidate answer can be tested efficiently.
The method requires a monotone feasibility condition. For minimization problems, the predicate often has the form:
For maximization problems, it often has the form:
Problem
Given a range of possible answers and a predicate , find the smallest feasible value such that
The predicate must be monotone: once a value becomes feasible, all larger values must also be feasible.
Key Idea
Convert an optimization problem into a decision problem.
Instead of asking:
ask:
Then use binary search to find the boundary between infeasible and feasible values.
Algorithm
binary_search_on_answer(lo, hi, feasible):
# Search in the inclusive range [lo, hi].
# Assumes at least one feasible value exists.
while lo < hi:
mid = lo + (hi - lo) // 2
if feasible(mid):
hi = mid
else:
lo = mid + 1
return loExample
Suppose we need the smallest capacity needed to ship packages within days.
A candidate capacity is feasible if the packages can be shipped in at most days using ships of capacity .
If capacity is feasible, then every larger capacity is also feasible. Therefore the predicate is monotone.
| capacity | feasible |
|---|---|
| too small | false |
| still too small | false |
| minimum valid capacity | true |
| larger capacity | true |
Binary search finds the first true capacity.
Correctness
The algorithm maintains the invariant that the optimal answer lies in the current interval .
If feasible(mid) is true, then mid is a valid answer, but a smaller valid answer may still exist, so the algorithm keeps the left half by setting hi = mid.
If feasible(mid) is false, then monotonicity implies that every value less than or equal to mid is also infeasible, so the algorithm removes them by setting lo = mid + 1.
When lo = hi, only one candidate remains. By the invariant, it is the smallest feasible answer.
Complexity
Let be the number of candidate values in the search range, and let be the cost of checking feasibility.
| cost | value |
|---|---|
| feasibility checks | |
| total time | |
| extra space |
For integer ranges, .
Choosing Bounds
Good bounds make the search safer and faster.
| bound | meaning |
|---|---|
lo | smallest possible answer |
hi | largest answer that is certainly feasible |
For the shipping capacity example:
lo = max(package_weight)hi = sum(package_weight)
The lower bound must rule out impossible answers. The upper bound must guarantee feasibility.
Common Patterns
| problem type | predicate shape | result |
|---|---|---|
| minimize maximum load | false then true | first feasible |
| minimize time | false then true | first feasible |
| maximize minimum distance | true then false | last feasible |
| maximize score under constraint | true then false | last feasible |
Implementation
def binary_search_on_answer(lo, hi, feasible):
while lo < hi:
mid = lo + (hi - lo) // 2
if feasible(mid):
hi = mid
else:
lo = mid + 1
return lofunc BinarySearchOnAnswer(lo, hi int, feasible func(int) bool) int {
for lo < hi {
mid := lo + (hi-lo)/2
if feasible(mid) {
hi = mid
} else {
lo = mid + 1
}
}
return lo
}