feat(patterns): tutorial system

This commit is contained in:
2025-08-07 00:41:51 +01:00
parent 1a1558cfad
commit 83bf313305
15 changed files with 1386 additions and 45 deletions

View File

@@ -0,0 +1,166 @@
name: Two Pointers
slug: two-pointers
difficulty_level: 2
description: >
Use two pointers to traverse data from different positions, often moving
toward or away from each other. This technique transforms O(n²) brute force
into O(n) by eliminating redundant comparisons.
when_to_use: |
- Sorted arrays where you need to find pairs
- Linked list cycle detection
- Removing duplicates in-place
- Partitioning arrays
- Palindrome checking
metaphor: |
Imagine two people reading a book from opposite ends, each moving toward the
middle. The person at the back skips ahead when they find what they're looking
for, while the person at the front moves forward when they don't match. They
meet somewhere in the middle, having searched the entire book without either
person reading the same page twice.
Another way to think about it: squeezing toothpaste from both ends of the
tube. You apply pressure from each side, working toward the center until
you've gotten everything out.
core_concept: |
The **two pointers** technique eliminates the need for nested loops by
maintaining two positions that move through the data based on conditions.
The key insight is that when data has *structure* (like being sorted), you
can make intelligent decisions about which pointer to move. If the current
pair is too small, moving the left pointer right increases the sum. If it's
too large, moving the right pointer left decreases it.
This reduces O(n²) brute force (checking all pairs) to O(n) because each
element is visited at most twice—once by each pointer.
visualization: |
**Example: Find pair with sum = 10 in sorted array**
```
Array: [1, 2, 4, 6, 8, 10] Target: 10
L R
Step 1: 1 + 10 = 11 > 10 → Sum too large, move R left
L R
Step 2: 1 + 8 = 9 < 10 → Sum too small, move L right
L R
Step 3: 2 + 8 = 10 ✓ → Found! Return [1, 4]
```
**Key insight**: Because the array is sorted, we know exactly which pointer
to move. Too big? Decrease the larger value. Too small? Increase the smaller.
code_template: |
def two_pointers(arr: list, target: int) -> list:
"""Two pointers converging from opposite ends."""
left, right = 0, len(arr) - 1
while left < right:
current = arr[left] + arr[right]
if current == target:
return [left, right] # Found!
elif current < target:
left += 1 # Need larger sum
else:
right -= 1 # Need smaller sum
return [] # No solution found
def two_pointers_same_direction(arr: list) -> int:
"""Two pointers moving in same direction (slow/fast)."""
slow = 0
for fast in range(len(arr)):
if some_condition(arr[fast]):
arr[slow] = arr[fast]
slow += 1
return slow # New length
recognition_signals:
- "sorted array"
- "find pair with sum"
- "two sum"
- "in-place modification"
- "remove duplicates"
- "partition array"
- "palindrome"
- "container with most water"
- "trapping rain water"
- "move zeros"
common_mistakes:
- title: Off-by-one with boundaries
description: |
Using `<=` instead of `<` when pointers should not overlap causes
infinite loops or double-counting elements.
fix: |
For converging pointers, use `while left < right`. Only use `<=` when
the same element can be part of the answer twice.
- title: Not handling duplicates
description: |
When the problem asks for unique pairs, forgetting to skip duplicate
values leads to repeated answers.
fix: |
After finding a match, skip over duplicates:
```python
while left < right and arr[left] == arr[left + 1]:
left += 1
```
- title: Moving both pointers at once
description: |
Moving both pointers simultaneously after finding a match can skip
valid solutions.
fix: |
Move one pointer at a time and let the next iteration decide the other.
After a match, move both only when you've recorded the result.
- title: Forgetting the sorted requirement
description: |
Two pointers only works predictably on sorted data. Applying it to
unsorted arrays gives wrong results.
fix: |
Sort first if needed (adds O(n log n)), or use a hash map approach
instead if sorting changes the problem semantics.
variations:
- name: Opposite-direction (converging)
description: |
Pointers start at opposite ends and move toward each other. Used for
pair problems in sorted arrays.
example: "Two Sum II, Container With Most Water, Valid Palindrome"
- name: Same-direction (fast-slow)
description: |
Both pointers start at the same end but move at different speeds or
based on different conditions. Used for in-place modifications.
example: "Remove Duplicates, Move Zeros, Remove Element"
- name: Sliding window variant
description: |
Two pointers defining a window that expands and contracts. Technically
a separate pattern but uses similar mechanics.
example: "Minimum Window Substring, Longest Substring Without Repeating"
- name: Three pointers
description: |
Extension with three pointers for problems involving triplets or
partitioning into three sections.
example: "3Sum, Sort Colors (Dutch National Flag)"
related_patterns:
- sliding-window
- fast-slow-pointers
- binary-search
prerequisite_patterns: []