Monotone Predicate Search
Find the boundary where a Boolean predicate changes value over an ordered domain.
Monotone Predicate Search
Monotone predicate search finds the transition point of a Boolean predicate over an ordered domain. It is the abstract form behind lower bound, upper bound, first true search, last true search, and binary search on answer.
A predicate is monotone if its values change at most once.
For first true search, the shape is:
$$ false, false, false, true, true, true $$
For last true search, the shape is:
$$ true, true, true, false, false, false $$
Problem
Given indices from $0$ to $n - 1$ and a monotone predicate $P(i)$, find the first index $i$ such that:
$$ P(i) = true $$
If no such index exists, return $n$.
Key Idea
Because the predicate changes value only once, the answer is a boundary. Binary search can locate that boundary without checking every index.
At each step, test the midpoint:
- If $P(m)$ is true, keep the left half
- If $P(m)$ is false, keep the right half
Algorithm
monotone_predicate_search(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
Suppose:
| index | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| P(i) | false | false | false | true | true | true |
The first true index is:
$$ 3 $$
The algorithm returns $3$.
Correctness
The algorithm maintains this invariant:
- The first true index, if it exists, lies in the half-open interval $[l, r)$.
If $P(m)$ is true, then $m$ is a valid true position, but an earlier true position may exist. Therefore the answer lies in $[l, m]$, and the algorithm sets $r = m$.
If $P(m)$ is false, monotonicity implies that every index at or before $m$ is false. Therefore the first true index must lie in $[m + 1, r)$, and the algorithm sets $l = m + 1$.
When $l = r$, the interval contains exactly one boundary position. That position is the first true index, or $n$ if no true value exists.
Complexity
Let $T_P$ be the cost of evaluating the predicate.
| cost | value |
|---|---|
| predicate evaluations | $O(\log n)$ |
| total time | $O(T_P \log n)$ |
| extra space | $O(1)$ |
Common Instances
| algorithm | predicate |
|---|---|
| lower bound | $A[i] \ge x$ |
| upper bound | $A[i] > x$ |
| binary search on answer | candidate value is feasible |
| bisection method | sign or threshold condition |
| Fenwick lower bound | prefix sum reaches target |
Last True Form
For a predicate shaped as:
$$ true, true, true, false, false, false $$
find the last true index by searching for the first false index and subtracting one.
last_true(n, P):
first_false = monotone_predicate_search(n, lambda i: not P(i))
return first_false - 1
If no index is true, the result is $-1$.
When to Use
Use monotone predicate search when:
- the domain is ordered
- the predicate has one transition
- checking a candidate is cheaper than scanning
- the desired answer is a boundary
Notes
- The hardest part is proving monotonicity.
- The predicate may be implicit and expensive.
- The method works on integers directly.
- For real domains, use a fixed number of iterations or a tolerance.
Implementation
def monotone_predicate_search(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 MonotonePredicateSearch(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
}