← Back to Algorithm series
πŸ”
Sorting & Searching
Hand-coded + bisect Β· lower/upper bound

11. Binary Search

Binary search reduces O(n) to O(log n) on sorted data. Master the recurrence by hand so you can apply it to non-trivial 'find the boundary' problems.

binary searchbisectlower bound
Duration
⏱ ~1 hour
Level
πŸ“Š Intermediate
Prerequisite
🎯 Lecture 9
OUTCOME
Solve any 'find the first/last/exact x' problem on a sorted array.

What you'll learn

  • 1Hand-code binary search avoiding off-by-one errors
  • 2Use bisect_left and bisect_right correctly
  • 3Distinguish exact-match, lower-bound, and upper-bound queries

Overview

Binary search divides the search space in half each step β€” O(log n). It only works on a sorted (or monotone) sequence. Edge cases (off-by-one, infinite loops) are the main source of bugs.

Core Concepts

1. Find exact match

python
def binary_search(a, target):
    lo, hi = 0, len(a) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if a[mid] == target:
            return mid
        elif a[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

2. Lower bound (first index >= target)

python
def lower_bound(a, target):
    lo, hi = 0, len(a)
    while lo < hi:
        mid = (lo + hi) // 2
        if a[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo   # may be len(a) if all < target

3. Upper bound (first index > target)

python
def upper_bound(a, target):
    lo, hi = 0, len(a)
    while lo < hi:
        mid = (lo + hi) // 2
        if a[mid] <= target:
            lo = mid + 1
        else:
            hi = mid
    return lo

4. The bisect module

python
import bisect
a = [1, 3, 5, 5, 5, 7, 9]

bisect.bisect_left(a, 5)    # 2 (first index of 5)
bisect.bisect_right(a, 5)   # 5 (first index > 5)
bisect.bisect_right(a, 5) - bisect.bisect_left(a, 5)   # count of 5s

bisect.insort(a, 4)         # insert 4 in sorted order
πŸ’‘

bisect is C-implemented and very fast. Always prefer it over hand-coding when you just need find/insert positions.

Examples

Example 1 β€” src/01_binary_search.py

Iterative and recursive binary search.

Example 2 β€” src/02_lower_upper.py

lower_bound and upper_bound by hand.

Example 3 β€” src/03_bisect_demo.py

bisect_left, bisect_right, insort.

Example 4 β€” src/04_search_2d.py

Search a row-and-column-sorted matrix.

Common Mistakes

  1. Forgetting hi = len(a) - 1 vs hi = len(a) inconsistency. Pick one convention and stick to it.
  2. (lo + hi) // 2 overflows in some languages β€” in Python it's fine, but be aware.
  3. Updating lo = mid (not mid+1) β†’ infinite loop when range = 2.
  4. Searching an unsorted array. Sort first or use a hash.

Recap

  • Pre-condition: sorted (or monotone) input.
  • bisect_left / bisect_right cover most needs.
  • Off-by-one bugs are common β€” test on boundary inputs.

Try It

  1. BOJ 1920 (Find a number).
  2. BOJ 10816 (Sorted card 2) β€” count occurrences.
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—