Binary Search Patterns
Apply binary search beyond sorted arrays to find boundaries and answers
Overview
Binary search repeatedly halves a search space, giving O(log n) time. Beyond finding an exact value in a sorted array, the real power is searching for a boundary: the first or last index that satisfies a condition. A related pattern, "binary search on the answer", searches a numeric range instead of an array whenever the feasibility test is monotonic.
Syntax / Usage
Keep a half-open interval [lo, hi) and shrink it based on a predicate. The invariant is that the answer always stays inside the current window, which avoids most off-by-one bugs.
def lower_bound(nums, target): # O(log n) time, O(1) space
lo, hi = 0, len(nums) # half-open interval [lo, hi)
while lo < hi:
mid = (lo + hi) // 2 # no overflow risk in Python
if nums[mid] < target:
lo = mid + 1 # mid too small, discard left half
else:
hi = mid # mid may be the answer, keep it
return lo # first index where nums[i] >= target
Examples
Find the insertion point (first index not less than the target):
print(lower_bound([1, 3, 3, 5, 8], 3)) # 1 (first 3)
print(lower_bound([1, 3, 3, 5, 8], 4)) # 3 (would insert before 5)
Binary search on the answer: minimum ship capacity to move packages in d days.
def min_capacity(weights, days): # O(n log(sum)) time
def feasible(cap):
need, load = 1, 0
for w in weights:
if load + w > cap:
need, load = need + 1, 0
load += w
return need <= days
lo, hi = max(weights), sum(weights)
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid):
hi = mid
else:
lo = mid + 1
return lo
print(min_capacity([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5)) # 15
Common Mistakes
- Mixing closed and half-open intervals, which produces off-by-one errors at the edges.
- Using
mid = (lo + hi) // 2fine in Python, but overflow-prone in languages with fixed ints. - Forgetting to prove the predicate is monotonic before searching on the answer.
- Looping with
lo <= hiand inclusivehi, then updating bounds inconsistently. - Returning
midinstead of a boundary when the problem wants the first/last match.
See Also
algorithms-searching algorithms-divide-and-conquer algorithms-two-pointers