Introduction#
At its core, binary search finds the boundary where a monotonic condition flips from False to True.
index: 0 1 2 3 4 5
condition: False False False True True True
↑ the position we want
Two common uses:
- Finding a position in a sorted array, such as the first index with value
>= x. - Binary search on the answer: the answer range is large, but you can write a monotonic
check(x)that says whetherxcan be the answer.
Template 1: Lower Bound / Upper Bound (half-open)#
I always use the half-open interval [lo, hi). When the loop ends, lo == hi is the answer:
def lower_bound(arr, x): # first index with value >= x
lo, hi = 0, len(arr) # hi is len(arr), not len(arr) - 1
while lo < hi:
mid = (lo + hi) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
def upper_bound(arr, x): # first index with value > x
lo, hi = 0, len(arr)
while lo < hi:
mid = (lo + hi) // 2
if arr[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
Python’s bisect.bisect_left and bisect.bisect_right are exactly these two functions.
Key Parameters:#
hi = len(arr): if every element is smaller thanx, the answer islen(arr), sohimust be able to equal it (LC 3508).- Parenthesize
mid = (lo + hi) // 2:lo + hi // 2computeshi // 2first (LC 3709). - Count inside
[a, b]=upper_bound(b) - lower_bound(a).
Template 2: Binary Search on the Answer#
Find the smallest x for which check(x) holds:
lo, hi = smallest possible answer, largest possible answer
while lo < hi:
mid = (lo + hi) // 2
if check(mid):
hi = mid # mid works; the answer is mid or to its left
else:
lo = mid + 1 # mid fails; the answer is to its right
return lo
Key Parameters:#
- Make sure
checkis monotonic: ifxworks, every value larger thanxworks too. - Make the upper bound large enough: LC 3824 first used
max(nums), but the answer can exceed it. - For a maximum, flip it: if
check(mid)holds, setlo = mid, and thenmidmust be(lo + hi + 1) // 2or the loop never ends. Alternatively, find the first failing position and subtract 1.
Template 3: K-th Smallest by Counting “≤ mid”#
Instead of finding the k-th smallest value directly, binary search the answer: the first mid for which “count of values <= mid” is >= k is the k-th smallest.
lo, hi = minimum value, maximum value
while lo < hi:
mid = (lo + hi) // 2
if count_at_most(mid) < k:
lo = mid + 1
else:
hi = mid
return lo
In LC 719, count_at_most uses a sliding window to count pairs with distance <= mid in O(n), for O(n log W) overall.
Template 4: Integer K-th Root#
x ** (1 / k) is floating-point math and can be off. For an exact floor(n^(1/k)), binary search on integers:
def kth_root_floor(n, k): # largest x with x ** k <= n
lo, hi = 0, n + 1
while lo < hi:
mid = (lo + hi) // 2
if mid ** k <= n:
lo = mid + 1
else:
hi = mid
return lo - 1
LC 3932 counts perfect k-th powers in [l, r]: kth_root_floor(r) - kth_root_floor(l - 1).
Template 5: Sort + Prefix Sums + bisect#
Many “sum or count over a range” queries become: sort, build prefix sums, and use bisect for the boundary on each query:
pairs = sorted(zip(keys, values))
sorted_keys = [k for k, _ in pairs]
prefix = [0]
for _, v in pairs:
prefix.append(prefix[-1] + v)
def query(limit): # sum of values with key <= limit
return prefix[bisect.bisect_right(sorted_keys, limit)]
LC 2838, 2300, and 3709 all follow this pattern.
Template 6: Rotated arrays#
Splitting at mid, at least one half is sorted; decide which, then whether the target lies in it:
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
if nums[l] <= nums[mid]: # left half sorted
if nums[l] <= target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else: # right half sorted
if nums[mid] < target <= nums[r]:
l = mid + 1
else:
r = mid - 1
- Search (LC 33): the code above; with duplicates (LC 81),
nums[l] == nums[mid]hides the sorted half, sol += 1. - Minimum (LC 153): compare
nums[mid]withnums[r]: larger meansl = mid + 1, otherwiser = mid; with duplicates (LC 154), equality meansr -= 1. - Peak (LC 162):
nums[mid] < nums[mid + 1]guarantees a peak to the right. - The single element among pairs (LC 540): before it, every pair starts at an even index.
Never Mix the Two Interval Styles#
Half-open [lo, hi) | Closed [lo, hi] | |
|---|---|---|
Initial hi | len(arr) | len(arr) - 1 |
| Loop condition | while lo < hi | while lo <= hi |
| Shrink left | hi = mid | hi = mid - 1 |
| Shrink right | lo = mid + 1 | lo = mid + 1 |
Pick one and stick to it. LC 1292 passed with the closed style; switching to while lo < hi without also changing hi = mid - 1 gave a WA.
Complexity#
- Binary search over an array of length
n:O(log n). - Binary search on the answer:
O(log(answer range) × cost of check).
Common Mistakes#
These are all mistakes I actually made while practicing:
mid = left + right // 2: operator precedence, missing parentheses (LC 3709).hi = len(arr) - 1with a half-open loop: the “insert at the end” position can never be returned (LC 3508).Mixing the two interval styles: changing the loop condition but not the updates (LC 1292).
An upper bound too small for binary search on the answer (LC 3824).
Taking a floating-point root and comparing for equality: in Python,
64 ** (1/3)is3.9999999999999996, not4.0(LC 3932).Using float division for a threshold:
success / spellcan lose precision with large numbers; use integerceil(LC 2300).Letting a two-pointer index move backwards:
j_low = min(i + 1, j_low)pulls the pointer back every time, turning it intoO(n^2)(LC 1712).mid = (l + r) / 2: Python 3’s/gives a float, unusable as an index (LC 704, 74, 35).Returning on an exact match while minimizing: a smaller value may also work (LC 875).
Comparing only
nums[mid]with the target in a rotated array: decide the sorted half first (LC 33, 81).Moving one step at a time:
l += 1/r -= 1is a linear search, TLE (LC 981).Testing existence with
start < end: a single occurrence has start equal to end (LC 34).while l < rwithl = mid:midrounds down,lnever moves, infinite loop (LC 441, 34).
Related LeetCode#
LC 2300Successful Pairs of Spells and PotionsLC 2838Maximum Coins Heroes Can CollectLC 3709Design Exam Scores TrackerLC 3508Implement RouterLC 3814Maximum Capacity Within BudgetLC 1712Ways to Split Array Into Three SubarraysLC 3639Minimum Time to Activate StringLC 3824Minimum K to Reduce Array Within LimitLC 1292Maximum Side Length of a Square with Sum Less than or Equal to ThresholdLC 719Find K-th Smallest Pair DistanceLC 3932Count K-th Roots in a RangeLC 327Count of Range SumLC 704Binary SearchLC 35Search Insert PositionLC 34Find First and Last Position of Element in Sorted ArrayLC 74Search a 2D MatrixLC 240Search a 2D Matrix IILC 33Search in Rotated Sorted ArrayLC 81Search in Rotated Sorted Array IILC 153Find Minimum in Rotated Sorted ArrayLC 154Find Minimum in Rotated Sorted Array IILC 162Find Peak ElementLC 875Koko Eating BananasLC 275H-Index IILC 1337The K Weakest Rows in a MatrixLC 268Missing NumberLC 367Valid Perfect SquareLC 69Sqrt(x)LC 888Fair Candy SwapLC 278First Bad VersionLC 374Guess Number Higher or LowerLC 441Arranging CoinsLC 287Find the Duplicate NumberLC 981Time Based Key-Value StoreLC 540Single Element in a Sorted ArrayLC 744Find Smallest Letter Greater Than Target
