Files
codetutor/backend/data/patterns/two-pointers.yaml

282 lines
9.9 KiB
YAML

name: Two Pointers
slug: two-pointers
difficulty_level: 2
pattern_type: algorithm
display_order: 1
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: []
visualization_examples:
- id: "find-pair-sum"
title: "Find pair with sum = 10"
input:
array: [1, 2, 4, 6, 8, 10]
target: 10
code: |
left, right = 0, len(arr) - 1
while left < right:
curr = arr[left] + arr[right]
if curr == target:
return [left, right]
elif curr < target:
left += 1
else:
right -= 1
steps:
- id: "1"
description: "Initialize pointers at both ends of the sorted array. Left starts at index 0 (value 1), right starts at index 5 (value 10)."
structures:
array:
type: array
values:
- { value: 1, state: active, annotations: ["L"] }
- { value: 2, state: default }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: default }
- { value: 10, state: active, annotations: ["R"] }
pointers: { left: 0, right: 5 }
variables: { left: 0, right: 5, target: 10 }
codeHighlight: { startLine: 1, endLine: 1 }
- id: "2"
description: "Calculate current sum: 1 + 10 = 11. This is greater than target (10), so we need a smaller sum. Move right pointer left."
structures:
array:
type: array
values:
- { value: 1, state: comparing, annotations: ["L"] }
- { value: 2, state: default }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: default }
- { value: 10, state: comparing, annotations: ["R"] }
pointers: { left: 0, right: 5 }
variables: { left: 0, right: 5, curr: 11, target: 10 }
codeHighlight: { startLine: 3, endLine: 8 }
- id: "3"
description: "Right pointer moved to index 4 (value 8). Now checking new sum."
structures:
array:
type: array
values:
- { value: 1, state: active, annotations: ["L"] }
- { value: 2, state: default }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: active, annotations: ["R"] }
- { value: 10, state: visited }
pointers: { left: 0, right: 4 }
variables: { left: 0, right: 4, target: 10 }
codeHighlight: { startLine: 8, endLine: 8 }
- id: "4"
description: "Calculate current sum: 1 + 8 = 9. This is less than target (10), so we need a larger sum. Move left pointer right."
structures:
array:
type: array
values:
- { value: 1, state: comparing, annotations: ["L"] }
- { value: 2, state: default }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: comparing, annotations: ["R"] }
- { value: 10, state: visited }
pointers: { left: 0, right: 4 }
variables: { left: 0, right: 4, curr: 9, target: 10 }
codeHighlight: { startLine: 3, endLine: 7 }
- id: "5"
description: "Left pointer moved to index 1 (value 2). Now checking new sum."
structures:
array:
type: array
values:
- { value: 1, state: visited }
- { value: 2, state: active, annotations: ["L"] }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: active, annotations: ["R"] }
- { value: 10, state: visited }
pointers: { left: 1, right: 4 }
variables: { left: 1, right: 4, target: 10 }
codeHighlight: { startLine: 7, endLine: 7 }
- id: "6"
description: "Calculate current sum: 2 + 8 = 10. This equals the target! We found our pair at indices [1, 4]."
structures:
array:
type: array
values:
- { value: 1, state: visited }
- { value: 2, state: found, annotations: ["L"] }
- { value: 4, state: default }
- { value: 6, state: default }
- { value: 8, state: found, annotations: ["R"] }
- { value: 10, state: visited }
pointers: { left: 1, right: 4 }
variables: { left: 1, right: 4, curr: 10, target: 10 }
codeHighlight: { startLine: 4, endLine: 5 }