Skip to content

Binary Search on Answer

Find an optimal value by binary searching a monotone feasibility condition.

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:

false,false,false,true,true,true false, false, false, true, true, true

For maximization problems, it often has the form:

true,true,true,false,false,false true, true, true, false, false, false

Problem

Given a range of possible answers and a predicate P(x)P(x), find the smallest feasible value xx such that

P(x)=true P(x) = true

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:

What is the best answer? \text{What is the best answer?}

ask:

Is this candidate answer feasible? \text{Is this candidate answer feasible?}

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 lo

Example

Suppose we need the smallest capacity needed to ship packages within dd days.

A candidate capacity cc is feasible if the packages can be shipped in at most dd days using ships of capacity cc.

If capacity cc is feasible, then every larger capacity is also feasible. Therefore the predicate is monotone.

capacityfeasible
too smallfalse
still too smallfalse
minimum valid capacitytrue
larger capacitytrue

Binary search finds the first true capacity.

Correctness

The algorithm maintains the invariant that the optimal answer lies in the current interval [lo,hi][lo, hi].

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 RR be the number of candidate values in the search range, and let TPT_P be the cost of checking feasibility.

costvalue
feasibility checksO(logR)O(\log R)
total timeO(TPlogR)O(T_P \log R)
extra spaceO(1)O(1)

For integer ranges, R=hilo+1R = hi - lo + 1.

Choosing Bounds

Good bounds make the search safer and faster.

boundmeaning
losmallest possible answer
hilargest 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 typepredicate shaperesult
minimize maximum loadfalse then truefirst feasible
minimize timefalse then truefirst feasible
maximize minimum distancetrue then falselast feasible
maximize score under constrainttrue then falselast 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 lo
func 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
}