name: Binary Search slug: binary-search difficulty_level: 2 pattern_type: algorithm display_order: 4 description: > Efficiently search sorted data by repeatedly dividing the search space in half. This transforms O(n) linear search into O(log n) by eliminating half the remaining possibilities with each comparison. when_to_use: | - Sorted arrays or search spaces - Finding boundaries (first/last occurrence) - Searching in rotated sorted arrays - Finding peak elements - Minimizing/maximizing with monotonic constraints metaphor: | Imagine playing a number guessing game where someone says "higher" or "lower" after each guess. The optimal strategy is always guessing the middle—you eliminate half the possibilities each time regardless of the answer. Another analogy: looking up a word in a physical dictionary. You don't read page by page from the start. You open roughly to the middle, see if you're before or after your word, then repeat in the appropriate half. core_concept: | Binary search works because the data has **monotonic ordering**—if you find something too small, everything before it is also too small. If something is too big, everything after is also too big. The key insight extends beyond simple arrays: 1. **Value search**: Find a specific target in sorted array 2. **Boundary search**: Find the first/last element satisfying a condition 3. **Search space**: Binary search over answers (e.g., "minimum capacity") At each step, you make one comparison and eliminate half the space. After k comparisons, you've narrowed n elements down to n/2^k. Solving n/2^k = 1 gives k = log₂(n). visualization: | **Example: Find target = 7 in sorted array** ``` Array: [1, 3, 5, 7, 9, 11, 13] L M R Step 1: mid = 7, target = 7 → Found! ``` **Example: Find target = 9** ``` [1, 3, 5, 7, 9, 11, 13] L M R Step 1: mid = 7 < 9 → Search right half L M R Step 2: mid = 11 > 9 → Search left half L M R Step 3: mid = 9 → Found at index 4! ``` **Binary search on answer: Minimum capacity to ship packages in D days** ``` Search space: [max(weights), sum(weights)] mid = some capacity Can ship in D days with capacity mid? Yes → try smaller capacity (go left) No → need more capacity (go right) ``` code_template: | def binary_search(arr: list, target: int) -> int: """Classic binary search for exact match.""" left, right = 0, len(arr) - 1 while left <= right: mid = left + (right - left) // 2 # Avoid overflow if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1 # Not found def lower_bound(arr: list, target: int) -> int: """Find first position where arr[i] >= target.""" left, right = 0, len(arr) while left < right: mid = left + (right - left) // 2 if arr[mid] < target: left = mid + 1 else: right = mid return left # First valid position def binary_search_answer(low: int, high: int, is_valid) -> int: """Binary search on answer space.""" while low < high: mid = low + (high - low) // 2 if is_valid(mid): high = mid # Try smaller else: low = mid + 1 # Need bigger return low recognition_signals: - "sorted array" - "O(log n)" - "find minimum/maximum" - "find first/last occurrence" - "rotated sorted array" - "peak element" - "minimum capacity" - "search space" - "lower bound" - "upper bound" common_mistakes: - title: Integer overflow in mid calculation description: | Using `(left + right) / 2` can overflow if left and right are both large positive integers (in languages with fixed-size integers). fix: | Use `left + (right - left) // 2` instead. This is mathematically equivalent but avoids overflow. - title: Infinite loop with wrong boundary update description: | Using `right = mid` with `left <= right` condition, or `left = mid` when mid could equal left, causes infinite loops. fix: | For `left <= right`, always use `left = mid + 1` and `right = mid - 1`. For `left < right`, use `left = mid + 1` and `right = mid`. - title: Off-by-one with boundary search description: | Returning `left` when you should return `left - 1` (or vice versa) gives the wrong boundary element. fix: | Think carefully about loop invariants. What does `left` represent when the loop ends? Test with edge cases. - title: Not recognizing binary search applicability description: | Missing that a problem can use binary search because the "array" is implicit (search space of possible answers). fix: | If you need to minimize/maximize something and can write a function `is_valid(x)` that's monotonic, binary search applies. variations: - name: Classic search description: | Find exact target in sorted array. Returns index or -1. example: "Binary Search, Search Insert Position" - name: Lower/Upper bound description: | Find first or last position satisfying a condition. Used for ranges and counting occurrences. example: "First Bad Version, Find First and Last Position" - name: Rotated array description: | Sorted array rotated at some pivot. One half is always sorted—determine which half and search appropriately. example: "Search in Rotated Sorted Array, Find Minimum" - name: Binary search on answer description: | Search the space of possible answers. Need a monotonic predicate function to determine feasibility. example: "Capacity To Ship Packages, Koko Eating Bananas, Split Array Largest Sum" - name: Peak finding description: | Find local maximum in bitonic array. Compare mid with neighbors to determine which side has the peak. example: "Find Peak Element, Find in Mountain Array" related_patterns: - two-pointers prerequisite_patterns: [] visualization_examples: - id: binary-search-find-target title: Find Target in Sorted Array input: nums: [1, 3, 5, 7, 9, 11, 13] target: 9 code: | def binary_search(nums, target): left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return -1 steps: - id: step-1 description: "Initialize pointers: left=0, right=6. Search space is entire array." structures: nums: type: array values: - { value: 1, state: default } - { value: 3, state: default } - { value: 5, state: default } - { value: 7, state: default } - { value: 9, state: default } - { value: 11, state: default } - { value: 13, state: default } pointers: left: 0 right: 6 variables: target: 9 left: 0 right: 6 codeHighlight: startLine: 2 endLine: 2 - id: step-2 description: "Calculate mid=3. nums[3]=7 < target=9, so search right half." structures: nums: type: array values: - { value: 1, state: visited } - { value: 3, state: visited } - { value: 5, state: visited } - { value: 7, state: comparing } - { value: 9, state: default } - { value: 11, state: default } - { value: 13, state: default } pointers: left: 0 mid: 3 right: 6 variables: target: 9 left: 0 right: 6 mid: 3 codeHighlight: startLine: 9 endLine: 10 - id: step-3 description: "Update left=4. New search space is indices 4-6." structures: nums: type: array values: - { value: 1, state: visited } - { value: 3, state: visited } - { value: 5, state: visited } - { value: 7, state: visited } - { value: 9, state: default } - { value: 11, state: default } - { value: 13, state: default } pointers: left: 4 right: 6 variables: target: 9 left: 4 right: 6 codeHighlight: startLine: 4 endLine: 4 - id: step-4 description: "Calculate mid=5. nums[5]=11 > target=9, so search left half." structures: nums: type: array values: - { value: 1, state: visited } - { value: 3, state: visited } - { value: 5, state: visited } - { value: 7, state: visited } - { value: 9, state: default } - { value: 11, state: comparing } - { value: 13, state: visited } pointers: left: 4 mid: 5 right: 6 variables: target: 9 left: 4 right: 6 mid: 5 codeHighlight: startLine: 11 endLine: 12 - id: step-5 description: "Update right=4. Search space narrowed to index 4 only." structures: nums: type: array values: - { value: 1, state: visited } - { value: 3, state: visited } - { value: 5, state: visited } - { value: 7, state: visited } - { value: 9, state: active } - { value: 11, state: visited } - { value: 13, state: visited } pointers: left: 4 right: 4 variables: target: 9 left: 4 right: 4 codeHighlight: startLine: 4 endLine: 4 - id: step-6 description: "Calculate mid=4. nums[4]=9 == target=9. Found at index 4!" structures: nums: type: array values: - { value: 1, state: visited } - { value: 3, state: visited } - { value: 5, state: visited } - { value: 7, state: visited } - { value: 9, state: found } - { value: 11, state: visited } - { value: 13, state: visited } pointers: mid: 4 variables: target: 9 mid: 4 result: 4 codeHighlight: startLine: 7 endLine: 8