Binary Search Basics
Binary search finds a value, or a boundary, by cutting the search range in half at every step. A sorted array of one million numbers needs at most 20 steps, because 220 is about one million.
The idea is simple, but the details are where most bugs live: which half to keep, whether to include mid
Step 1: The Idea
Guess the middle of the range and compare. Because the data is sorted, one comparison tells you which half cannot contain the target, and that half is thrown away. Searching for 9 in [-1, 0, 3, 5, 9, 12]
| Range (indexes) | mid | nums[mid] | Decision |
|---|---|---|---|
| 0 to 5 | 2 | 3 | 3 < 9: keep the right half, 3 to 5 |
| 3 to 5 | 4 | 9 | found at index 4 |
Each step halves the range, so n elements need about log2 n steps: O(log n).
Step 2: Template 1, Find an Exact Value
When you are looking for one specific value, keep a closed range [lo, hi]
- Start with
andlo = 0 .hi = n - 1 - Loop while
, that is, while the range is not empty.lo <= hi - If
is the target, return it. If it is smaller, the target can only be to the right, sonums[mid] . If it is larger,lo = mid + 1 .hi = mid - 1 - If the loop ends, the range is empty and the target is not there.
Both updates move past midmid
Step 3: Template 2, Find the First Position Where Something Becomes True
Many problems do not ask "where is x?" but "where does something start?": the first value that is at least x, the first bad version, the smallest speed that is fast enough. Write the question as a yes/no test for each position. Because the data is sorted, the answers line up like this:
false, false, false, true, true, true
The goal is the first true[lo, hi)hi
- Loop while
.lo < hi - If the test is true at
, the first true ismid or earlier:mid . Do not skiphi = mid , it may be the answer.mid - If the test is false at
, the first true is after it:mid .lo = mid + 1 - When
, that position is the answer.lo == hi
This template has no "found it" case and no special exit, so it is hard to get wrong. Written once with a test function, it solves many problems:
import java.util.function.IntPredicate;
public class FirstTrue {
// Smallest x in [lo, hi] for which ok(x) is true, or hi + 1 if there is none.
// Requires ok to be monotone: false, false, ..., false, true, true, ..., true
public static int firstTrue(int lo, int hi, IntPredicate ok) {
int left = lo;
int right = hi + 1; // hi + 1 stands for "never true"
while (left < right) {
int mid = left + (right - left) / 2;
if (ok.test(mid)) {
right = mid; // mid is true: the answer is mid or earlier
} else {
left = mid + 1; // mid is false: the answer is after mid
}
}
return left;
}
public static void main(String[] args) {
int[] nums = {1, 3, 3, 5, 8};
// first index whose value is >= 3
System.out.println(firstTrue(0, nums.length - 1, i -> nums[i] >= 3)); // 1
// smallest x with x * x >= 50
System.out.println(firstTrue(0, 50, x -> x * x >= 50)); // 8
}
}
Step 4: Binary Search on the Answer
Binary search does not need an array. It works on any range of candidate answers where a yes/no test changes only once. Take
- If speed 4 is fast enough, every speed above 4 is also fast enough.
- If speed 3 is too slow, every speed below 3 is too slow.
So the answers for speeds 1, 2, 3, ... look like false, false, false, true, true, ...
- Find the smallest and largest possible answers.
- Write a function that checks one candidate answer, usually in O(n).
- Binary search for the first candidate that passes.
The total is O(n log R), where R is the size of the answer range. This one idea solves a large group of problems: shipping capacity, book allocation, bouquets, divisors, train speeds and more.
Step 5: The Classic Mistakes
- Overflow in mid.
overflows when both are large, which is why it is written(lo + hi) / 2 . Java's ownlo + (hi - lo) / 2 had this bug until 2006.Arrays.binarySearch - An infinite loop. If one branch sets
, round mid up withlo = mid . Otherwise, with two elements left, mid equals lo and the range never shrinks.lo + (hi - lo + 1) / 2 - Mixing the templates.
goes withlo <= hi ;hi = mid - 1 goes withlo < hi . Combininghi = mid withlo <= hi loops forever.hi = mid - Overflow in the test. Checks like
overflow anmid * mid <= x ; compute them inint .long
For a sorted array, Java has Arrays.binarySearch-(insertion point) - 1
Problems by Pattern
| Pattern | Problems |
|---|---|
| Find an exact value | |
| First or last position (boundaries) | |
| Arrays that are not fully sorted | |
| Binary search on the answer | |
| Binary search on a value, counting below it | |
| Binary search on a split point |