Skip to main content

Binary Search

Mindmap​

Binary Search
What are we searching?
An index
value lives in the array
A boundary
bisect left / right
An answer value
search on the answer
Pitfalls
lo < hi vs lo <= hi
hi = len vs len - 1
duplicates
glide left or right
Precondition
Sorted input
or sortable in O(N log N)
Monotone predicate
false…false true…true
The Invariant
lower-bound template
Range [lo, hi]
answer always inside
Loop while lo < hi
stop at one survivor
mid = lo + (hi - lo) // 2
left-biased, overflow-safe
Move
too small
lo = mid + 1
candidate
hi = mid
+
−
⟲
⛶

Problem Categories​

Binary Search
O(log N), O(sort)
Classic
Bisect toolkit
left / right
reversed
Boundaries
first & last
Slight variants
derived predicate
Math - square
Missing elements
Rotated
find pivot, split
Peaks
walk uphill
H-Index
Monotone Function
Split input array
Kth smallest
Secondary Loop
Bisect derivatives
2D matrix
Intersection
Custom bisect
+
−
⟲
⛶

Anatomy: One Template to Rule Them All​

Almost every problem here is the same lower-bound loop with the comparison swapped out. Master this shape and the variants become trivial.

lo, hi = 0, len(arr) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if predicate(mid): # mid is too small / not yet the answer
lo = mid + 1
else: # mid could be the answer - keep it
hi = mid
return lo # lo == hi: the leftmost index where predicate is False

The only things that change between problems:

KnobOptionsWhy it changes
What predicate(mid) testsarr[mid] < target, cited(mid) > arr[mid], canNotShip(mid), ...The "thing" being searched - a value, a boundary, or a feasibility answer
hi startlen(arr) - 1 vs len(arr)Whether the answer can sit one past the last index (insert positions, missing counts)
lo < hi vs lo <= hisurvivor check vs in-loop returnLower-bound style returns one survivor; the <= style returns on a direct hit
Glide direction< for leftmost, <= for rightmostHow duplicates of target are handled
  • An index - the value lives directly in a sorted array (704, 35, peaks, rotated).
  • A boundary - we want where a value would go, then read the [left, right) span (the bisect_* toolkit, 34).
  • An answer value - the array isn't searched at all; we bisect a numeric range and verify each candidate with a monotone predicate (875, 1011, sqrt).

When the input isn't sorted​

If the predicate is monotone but the array isn't, an O(N log N) sort up front still lands us in O(N log N) overall - which is why "sort, then bisect" appears throughout this section.