feat(patterns): tutorial system

This commit is contained in:
2025-08-07 00:41:51 +01:00
parent b4e23cc641
commit 2360727e11
15 changed files with 1386 additions and 45 deletions

View File

@@ -0,0 +1,202 @@
name: Binary Search
slug: binary-search
difficulty_level: 2
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: []