Skip to content

Last True Binary Search

Find the last position where a monotone predicate remains true.

Last true binary search finds the last index where a monotone Boolean predicate is true. It is the mirror form of first true binary search.

The predicate must have the form:

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

Once the predicate becomes false, it must remain false.

Problem

Given indices from 00 to n1n - 1 and a predicate P(i)P(i), find the largest index ii such that

P(i)=true P(i) = true

If no such index exists, return 1-1.

Key Idea

Maintain a half-open interval [l,r)[l, r) where the answer may exist. Test the midpoint, but bias the update toward the right side when the predicate is true.

If P(m)P(m) is true, then mm is a valid answer, and the last true position may be at mm or to its right. If P(m)P(m) is false, the answer must be to the left.

Algorithm

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

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

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

    return l - 1

Example

Let the predicate values be:

index012345
P(i)truetruetruetruefalsefalse

The last true index is 33.

The algorithm returns 41=34 - 1 = 3.

Relation to Upper Bound

If a sorted array is searched for the last element satisfying

A[i]x A[i] \le x

then the predicate is true first and false later. Last true search returns the last index whose value is at most xx.

Equivalently:

last_true(A[i]x)=upper_bound(A,x)1 \text{last\_true}(A[i] \le x) = \text{upper\_bound}(A, x) - 1

Correctness

The algorithm maintains this invariant:

  • All indices before ll have already been accepted as true candidates.
  • All indices from rr onward have already been rejected or lie after the transition.
  • The transition from true to false lies inside [l,r)[l, r).

When P(m)P(m) is true, monotonicity implies that every index before mm is also true, so the last true index must be at mm or to the right. The algorithm moves ll to m+1m + 1.

When P(m)P(m) is false, monotonicity implies that every index after mm is also false, so the last true index must be to the left. The algorithm moves rr to mm.

When the loop terminates, l=rl = r is the first false index. Therefore the last true index is l1l - 1.

Complexity

costvalue
predicate evaluationsO(logn)O(\log n)
timeO(lognTP)O(\log n \cdot T_P)
spaceO(1)O(1)

Here TPT_P is the cost of evaluating the predicate.

Use Cases

  • Last valid prefix length
  • Last index with value x\le x
  • Maximum feasible answer
  • Last successful test case
  • Right boundary of a sorted range

Implementation

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