Last True Binary Search
Find the last position where a monotone predicate remains true.
Last True Binary Search
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 $$
Once the predicate becomes false, it must remain false.
Problem
Given indices from $0$ to $n - 1$ and a predicate $P(i)$, find the largest index $i$ such that
$$ P(i) = true $$
If no such index exists, return $-1$.
Key Idea
Maintain a half-open interval $[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)$ is true, then $m$ is a valid answer, and the last true position may be at $m$ or to its right. If $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:
| index | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| P(i) | true | true | true | true | false | false |
The last true index is $3$.
The algorithm returns $4 - 1 = 3$.
Relation to Upper Bound
If a sorted array is searched for the last element satisfying
$$ 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 $x$.
Equivalently:
$$ \text{last_true}(A[i] \le x) = \text{upper_bound}(A, x) - 1 $$
Correctness
The algorithm maintains this invariant:
- All indices before $l$ have already been accepted as true candidates.
- All indices from $r$ onward have already been rejected or lie after the transition.
- The transition from true to false lies inside $[l, r)$.
When $P(m)$ is true, monotonicity implies that every index before $m$ is also true, so the last true index must be at $m$ or to the right. The algorithm moves $l$ to $m + 1$.
When $P(m)$ is false, monotonicity implies that every index after $m$ is also false, so the last true index must be to the left. The algorithm moves $r$ to $m$.
When the loop terminates, $l = r$ is the first false index. Therefore the last true index is $l - 1$.
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
- Last valid prefix length
- Last index with value $\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
}