Binary Search
1. Why a Recipe Matters
Binary search is unusually easy to almost get right. One reusable recipe leaves more attention for the problem-specific predicate and return value.
2. The Transition-Point Recipe
Reframe the search space as two monotonic regions:
before before before before | after after after
^
transition point
Define is_before(x) so every “before” candidate comes before every “after” candidate. The values themselves do not need to be booleans; only this classification must be monotonic.
Recipe 1
Find the boundary between “before” and “after”
target = 13, so is_before(i) = nums[i] < 13.1. Establish the invariant
left is known before; right is known after.
2. Shrink the unknown gap
Move exactly one endpoint to mid without breaking the invariant.
3. Stop when adjacent
left is the last before; right is the first after.
transition_point_recipe(first, last, is_before):
handle an empty range
left, right = first, last
handle left already being "after" # the whole range is after
handle right still being "before" # the whole range is before
while right - left > 1:
mid = left + (right - left) // 2
if is_before(mid):
left = mid
else:
right = mid
# left = last "before"
# right = first "after"
return whichever boundary the problem asks for
The loop never changes:
leftremains known “before,” andrightremains known “after.”midis strictly between them, so every iteration makes progress.- When they are adjacent,
leftandrightidentify the transition.
For classic search, use is_before(i) = nums[i] < target. Then right is the first value >= target; check it for equality. For an upper bound, use nums[i] <= target instead.
3. Practice the Recipe on Real Problems
For each problem, first state the “before” region, the “after” region, and whether the answer comes from left, right, or a final validation. Rotated arrays, valley bottoms, and overtaking points change those choices—not the loop.
4. Reusable Idea: Grid Flattening
If an R x C grid is sorted in complete row-major order, imagine it as one virtual sorted array of length R * C. Do not allocate that array; translate indices only when reading a cell:
- Grid to virtual index:
i = r * C + c. - Virtual index to grid:
r = i // C,c = i % C.
Reusable idea
Treat an R × C grid as one array
grid[1][2] = 9
i = 1 × 4 + 2 = 6
Grid → virtual index
i = r * C + cVirtual index → grid
(r, c) = (i // C, i % C)This produces an search with extra space. It is valid only when each row is sorted and the last value of one row is smaller than the first value of the next row.
5. Reusable Idea: Exponential Search and Guess-and-Check
When a search range has no known upper bound, probe 1, 2, 4, 8, ... until the predicate changes. The last two probes bracket the transition; then apply the normal recipe inside that interval.
Reusable idea
Double until the transition is bracketed
The unknown transition is now inside (8, 16].
Run the normal transition-point recipe only inside that bracket.
1. Guess
Probe 1, 2, 4, 8, ....
2. Check
Stop when the predicate changes state.
3. Refine
Binary search between the last two probes.
left, right = 0, 1
while is_before(right):
left = right
right *= 2
# The transition is now in (left, right].
run transition_point_recipe(left, right, is_before)
This pairs naturally with guess-and-check:
- Guess a candidate answer.
- Check whether it is too small, too large, feasible, or infeasible.
- Prove those answers form one monotonic transition.
- Binary search for the first or last candidate that satisfies the constraint.
Use this when checking one candidate is much easier than constructing the optimum directly. If the answer bounds are unknown, exponential search can discover them first.
6. Conclusion
The central idea is simple: every binary-search solution can be reframed as finding a transition point. Keep one trusted recipe and spend your problem-solving effort on three questions:
- What is the ordered search space?
- What does
is_before(x)mean, and why is it monotonic? - Does the problem need the last “before,” the first “after,” or a value derived from one?
Two useful extensions cover many less-obvious cases: flatten a globally sorted grid into virtual indices, and use repeated doubling when a search bound is unknown. With those tools, binary search becomes a reusable building block instead of a collection of fragile special cases.
Practice Problems
Binary Search
LeetCode: https://leetcode.com/problems/binary-search/
Search Insert Position
LeetCode: https://leetcode.com/problems/search-insert-position/
Find Minimum in Rotated Sorted Array
LeetCode: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
Search in Rotated Sorted Array
LeetCode: https://leetcode.com/problems/search-in-rotated-sorted-array/
Beyond Cracking the Coding Interview
Valley Bottom
A valley-shaped array is an array of integers such that:
2-Array 2-Sum
You are given two non-empty arrays of integers, sorted_arr and unsorted_arr. The first one is sorted, but the second is not. The goal is to find one element from each array with sum 0. If you can find them, return an array with their indices, starting with the element in sorted_arr. Otherwise, return [-1, -1]. Use O(1) extra space and do not modify the input.
Target Count Divisible By K
Given a sorted array of integers, arr, a target value, target, and a positive integer, k, return whether the number of occurrences of the target in the array is a multiple of k.
Race Overtaking
You are given two arrays of positive integers, p1 and p2, representing players in a racing game. The two arrays are sorted, non-empty, and have the same length, n. The i-th element of each array corresponds to where that player was on the track at the i-th second of the race. We know that:
Search in Sorted Grid
You're given a 2D grid of integers, grid, where each row is sorted (without duplicates), and the last value in each row is smaller than the first value in the following row. You are also given a target value, target. If the target is in the grid, return an array with its row and column indices. Otherwise, return [-1, -1].
Search in Huge Array
We are trying to search for a target integer, target, in a sorted array of integers (duplicates allowed) that is too big to fit into memory. We can only access the array through an API, fetch(i), which returns the value at index i if i is within bounds or -1 otherwise. Using as few calls to the API as possible, return the index of the target, or -1 if it does not exist. If the target appears multiple times, return any of the indices. There is no API to get the array's length.
Min-Subarray-Sum Split
LeetCode: https://leetcode.com/problems/split-array-largest-sum/
Water Refilling
We have an empty container with a capacity of a gallons of water and another container with a capacity of b gallons. Return how many times you can pour the second container full of water into the first one without overflowing. Assume that a > b.
Min Pages Per Day
You have upcoming interviews and have selected specific chapters from BCtCI to read beforehand. Given an array, page_counts, where each element represents a chapter's page count, and the number of days, days, until your interview, determine the minimum number of pages you must read daily to finish on time. Assume that:
Tide Aerial View
You are provided a series of aerial-view pictures of the same coastal region, taken a few minutes apart. Each picture consists of an n x n binary grid, where 0 represents land (above water) and 1 represents water (below water).