Skip to content

Monotone Predicate Search

Find the boundary where a Boolean predicate changes value over an ordered domain.

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 false, false, false, true, true, true

For last true search, the shape is:

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

Problem

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

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

If no such index exists, return nn.

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)P(m) is true, keep the left half
  • If P(m)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:

index012345
P(i)falsefalsefalsetruetruetrue

The first true index is:

3 3

The algorithm returns 33.

Correctness

The algorithm maintains this invariant:

  • The first true index, if it exists, lies in the half-open interval [l,r)[l, r).

If P(m)P(m) is true, then mm is a valid true position, but an earlier true position may exist. Therefore the answer lies in [l,m][l, m], and the algorithm sets r=mr = m.

If P(m)P(m) is false, monotonicity implies that every index at or before mm is false. Therefore the first true index must lie in [m+1,r)[m + 1, r), and the algorithm sets l=m+1l = m + 1.

When l=rl = r, the interval contains exactly one boundary position. That position is the first true index, or nn if no true value exists.

Complexity

Let TPT_P be the cost of evaluating the predicate.

costvalue
predicate evaluationsO(logn)O(\log n)
total timeO(TPlogn)O(T_P \log n)
extra spaceO(1)O(1)

Common Instances

algorithmpredicate
lower boundA[i]xA[i] \ge x
upper boundA[i]>xA[i] > x
binary search on answercandidate value is feasible
bisection methodsign or threshold condition
Fenwick lower boundprefix sum reaches target

Last True Form

For a predicate shaped as:

true,true,true,false,false,false 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-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
}