Search for a boundary by trying decreasing jumps whose lengths are powers of two.
Powers of two search finds a boundary in a monotone predicate by building the answer from large jumps to small jumps. It is a binary search variant written as controlled forward movement.
Instead of maintaining an interval, the algorithm keeps a current position and tries jumps of size:
This style is common in Fenwick trees, binary lifting, implicit arrays, and search over answer spaces.
Problem
Given a monotone predicate over indices to , find the largest index such that:
Assume the predicate has the shape:
If no index satisfies the predicate, return .
Key Idea
Start before the array at position . Try to jump forward by the largest power of two that may fit.
If the new position still satisfies , accept the jump. Otherwise, reject it and try a smaller jump.
By the end, no positive jump can be added while preserving the predicate.
Algorithm
powers_of_two_search(n, P):
pos = -1
step = 1
while step < n:
step = step * 2
while step > 0:
next = pos + step
if next < n and P(next):
pos = next
step = step // 2
return posExample
Suppose:
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| P(i) | true | true | true | true | false | false | false |
The largest true index is .
The algorithm tries large jumps first, accepts only jumps that remain true, and ends at index .
Relation to Binary Search
Standard binary search keeps an interval. Powers of two search keeps a position and a decreasing jump size.
Both rely on the same monotonicity condition and both take logarithmic time.
| method | state | decision |
|---|---|---|
| binary search | interval | keep left or right half |
| powers of two search | current position plus jump | accept or reject jump |
Correctness
The algorithm maintains this invariant:
posis either or an index satisfying .- No accepted jump ever crosses into a known false region.
When a candidate next = pos + step satisfies , accepting the jump keeps pos valid and moves it closer to the largest true index.
When the candidate is out of bounds or false, monotonicity implies that this jump is too large from the current position, so the algorithm safely rejects it and tries smaller jumps.
After all powers of two have been tried, no remaining positive jump can be accepted. Therefore pos is the largest true index.
Complexity
| cost | value |
|---|---|
| predicate evaluations | |
| time | |
| space |
Here is the cost of evaluating .
Common Uses
- Fenwick tree lower bound
- Binary lifting on trees
- Search over prefix sums
- Largest feasible answer
- Finding the last valid position in an implicit structure
Minimum True Variant
To find the first true index for a predicate shaped as:
search for the last false index, then add one.
first_true_by_powers_of_two(n, P):
last_false = powers_of_two_search(n, lambda i: not P(i))
return last_false + 1If all values are false, this returns .
Implementation
def powers_of_two_search(n, pred):
pos = -1
step = 1
while step < n:
step *= 2
while step > 0:
nxt = pos + step
if nxt < n and pred(nxt):
pos = nxt
step //= 2
return posfunc PowersOfTwoSearch(n int, pred func(int) bool) int {
pos := -1
step := 1
for step < n {
step *= 2
}
for step > 0 {
next := pos + step
if next < n && pred(next) {
pos = next
}
step /= 2
}
return pos
}