Binary Search Pattern: Algorithmic Intuition, Sub-patterns & C++ Guide
If you can answer "is the answer on the left or the right?" in one check, you can halve the search space every step.
4 sub-patterns Β· 15 questions
What is this pattern?
Binary Search means throwing away half of the remaining options with one check, again and again, until one is left. N options take only about logβ N checks β a million options need about 20.
It does not really need a sorted array. It needs a yes/no question whose answers, across the search space, look like this:
NO NO NO NO YES YES YES
Once answers are "all NOs, then all YESes", checking the middle tells you which half the first YES is in. That's the whole pattern. A sorted array is just the most common place this shape appears (nums[i] >= target?).
The search space can be:
- indices of an array (classic, rotated, peak),
- possible answers (the smallest speed, capacity or day that works),
- cut positions (where to split two sorted arrays).
Guessing a number between 1 and 100 when the other person says only "higher" or "lower". You'd never guess 1, 2, 3β¦ β you'd guess 50, then 25 or 75, and you'd always win within 7 guesses. Each answer rules out half of what's left.
See it move
Find the first index where nums[i] β₯ 23. The shaded part is what's still possible; red cells were ruled out without being looked at.
nums = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], target = 23step 1/5- target
- 23
- lo
- 0
- hi
- 10 (= n, "none")
Ask "is nums[i] β₯ 23?" for every i and the answers are all NOs, then all YESes. We want the first YES β without asking all 10 questions.
Koko: piles [3, 6, 7, 11], 8 hours. The track is every possible speed; ok? fills in as speeds are checked.
piles = [3, 6, 7, 11], h = 8step 1/6- piles
- [3, 6, 7, 11]
- hours allowed
- 8
- range
- 1 β¦ 11
We don't search the piles β we search the speeds. Speed 11 surely works (one hour per pile), and speed 1 is the slowest possible.
How do I recognise it?
The input is sorted (or sorted then rotated) and you need a value or a position.
"Find the first / last position where β¦", "insert position", "lower bound".
"Minimum speed / capacity / days / largest sum such that it's possible" β and checking one guess is easy.
The required time is O(log N), or N is huge (10βΉ) so you can't even loop over it.
You can say "if X works, every bigger X works too" (monotonic).
Looks like Binary Search, but isn't
You could binary search each element's partner (O(N log N)), but two pointers does it in O(N) by moving both ends.
Without a NNNNYYYY shape, checking the middle tells you nothing about which half to keep. Sorting first costs O(N log N) β only worth it for many searches.
Rows don't continue each other, so it isn't one sorted list. From the top-right corner each comparison removes a whole row or column: O(m + n).
Binary search on the answer only works if "X works β X + 1 works". If a bigger guess can fail after a smaller one worked, halving throws away real answers.
How would I discover it from scratch?
Don't memorise βuse binary searchβ. Ask these questions in order β the pattern falls out.
Check every option one by one: every index for the target, or every speed 1, 2, 3, β¦ until one works. O(N), or O(answer range Γ cost of a check).
Sub-patterns
Same core idea, different rule for βvalidβ. Learn them in this order.
Classic / boundaries
4 questionsA sorted array: find a value, or the first / last position where a condition flips from false to true.
Rotated / peak
4 questionsThe array isn't fully sorted, but some comparison at mid still tells you which half to keep: one half of a rotated array is always sorted, and a rising slope always leads to a peak.
Binary search on the answer
4 questionsYou aren't searching the input β you're searching the range of possible answers. Guess one, check it greedily, keep the half that still contains the best answer.
2D and partition search
3 questionsSearch a sorted matrix as one flat sorted array, or binary search where to cut two sorted arrays so the left parts hold exactly half of everything.
Don't mix these up
Looking for an exact value that may not exist β <=, and both sides move past mid. Looking for a boundary that always exists β <, with hi = mid so mid stays a candidate.
Is there an array you index into with mid? If mid is a speed, a capacity or a day β something you then test β you're searching the answer range.
153 has no target: compare nums[mid] with nums[hi] to find the drop. 33 has a target: find the sorted half, then ask whether the target falls inside it.
74: each row continues the previous one β one flat sorted array. 240: only rows and columns are sorted β staircase from the top-right corner.
Same problem in different words: packages β numbers, days β parts, capacity β largest part sum. Same range [max, sum], same greedy check, same lo < hi loop.
Code templates (C++)
Find a value
Invariant: if the target exists, it's inside [lo, hi]. mid = lo + (hi β lo) / 2 avoids overflow.
// Find target in a sorted array. Returns its index, or -1.
// Invariant: if target exists, it is inside [lo, hi].
int findValue(vector<int>& nums, int target) {
int lo = 0, hi = (int)nums.size() - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2; // same as (lo + hi) / 2, but can't overflow
if (nums[mid] == target) return mid;
if (nums[mid] < target) lo = mid + 1; // target can only be to the right
else hi = mid - 1; // target can only be to the left
}
return -1;
}First index where a condition becomes true
The workhorse. hi starts at n so "no index works" returns n. Lower bound, upper bound and insert position are all this with a different ok.
// First index i where ok(i) is true, for answers shaped false...false true...true.
// Returns n if no index works. Example ok: nums[i] >= target (= lower bound / insert position).
int firstTrue(vector<int>& nums, int target) {
auto ok = [&](int i) { return nums[i] >= target; };
int lo = 0, hi = (int)nums.size(); // hi = n means "none of them"
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (ok(mid)) hi = mid; // mid works: the first true is mid or to its left
else lo = mid + 1; // mid fails: the first true is right of mid
}
return lo; // lo == hi: the boundary
}Binary search on the answer
Only lo, hi and feasible() change between problems. Make lo the smallest answer that could ever work, hi one that surely works.
// Smallest speed k so that all piles are eaten within h hours (Koko).
// Only the range [lo, hi] and feasible() change between problems.
bool feasible(vector<int>& piles, int h, int k) {
long long hours = 0;
for (int p : piles) hours += (p + k - 1) / k; // ceil(p / k)
return hours <= h;
}
int smallestWorking(vector<int>& piles, int h) {
int lo = 1; // smallest answer that could ever work
int hi = *max_element(piles.begin(), piles.end()); // this one surely works
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (feasible(piles, h, mid)) hi = mid; // works: maybe something smaller works too
else lo = mid + 1; // fails: everything smaller fails too
}
return lo;
}Rotated sorted array
Compare with nums[hi], not nums[lo], so an array that isn't rotated at all still works.
// Index of the minimum in a rotated sorted array (distinct values).
int findMinIndex(vector<int>& nums) {
int lo = 0, hi = (int)nums.size() - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] > nums[hi]) lo = mid + 1; // the drop is right of mid
else hi = mid; // mid..hi is sorted: the min is mid or left of it
}
return lo;
}