First True Binary Search

Find the first position where a monotone predicate becomes true.

First True Binary Search

First true binary search finds the first index where a monotone Boolean predicate becomes true. It generalizes lower bound from array values to any ordered search space.

The predicate must have the form:

$$ false, false, false, true, true, true $$

Once the predicate becomes true, it must remain true.

Problem

Given indices from $0$ to $n - 1$ and a predicate $P(i)$, find the smallest index $i$ such that

$$ P(i) = true $$

If no such index exists, return $n$.

Key Idea

Maintain a half-open interval $[l, r)$ that contains the first true position. At each step, test the midpoint.

If $P(m)$ is true, the answer is at $m$ or to its left. Otherwise, the answer is to the right.

Algorithm

first_true(n, P):
    l = 0
    r = n

    while l < r:
        m = l + (r - l) // 2

        if P(m):
            r = m
        else:
            l = m + 1

    return l

Example

Let the predicate values be:

index 0 1 2 3 4 5
P(i) false false false true true true

The first true index is $3$.

Relation to Lower Bound

Lower bound is a special case where

$$ P(i) \equiv A[i] \ge x $$

So:

lower_bound(A, x):
    return first_true(length(A), lambda i: A[i] >= x)

Correctness

The invariant is:

  • The answer is always inside $[l, r)$

When $P(m)$ is true, no index greater than $m$ is needed to find the first true position, so the right boundary moves to $m$.

When $P(m)$ is false, monotonicity implies that every index $\le m$ is false, so the left boundary moves to $m + 1$.

When the interval is empty, $l = r$, and that position is the first true index or $n$ if none exists.

Complexity

cost value
predicate evaluations $O(\log n)$
time $O(\log n \cdot T_P)$
space $O(1)$

Here $T_P$ is the cost of evaluating the predicate.

Use Cases

  • Lower bound in sorted arrays
  • Minimum feasible value
  • First failing test
  • First time a threshold is reached
  • First day when a condition holds

Implementation

def first_true(n, pred):
    l, r = 0, n
    while l < r:
        m = l + (r - l) // 2
        if pred(m):
            r = m
        else:
            l = m + 1
    return l
func FirstTrue(n int, pred func(int) bool) int {
    l, r := 0, n
    for l < r {
        m := l + (r-l)/2
        if pred(m) {
            r = m
        } else {
            l = m + 1
        }
    }
    return l
}