486 lines
13 KiB
YAML
486 lines
13 KiB
YAML
name: Fast & Slow Pointers
|
|
slug: fast-slow-pointers
|
|
difficulty_level: 2
|
|
pattern_type: algorithm
|
|
display_order: 3
|
|
|
|
description: >
|
|
Use two pointers moving at different speeds to detect cycles, find midpoints,
|
|
or identify patterns in sequences. The fast pointer advances twice as quickly,
|
|
allowing detection of structural properties without extra space.
|
|
|
|
when_to_use: |
|
|
- Detecting cycles in linked lists or sequences
|
|
- Finding the middle of a linked list
|
|
- Finding the start of a cycle
|
|
- Happy number problem
|
|
- Palindrome linked list verification
|
|
|
|
metaphor: |
|
|
Imagine two runners on a circular track. If one runs twice as fast as the other,
|
|
the fast runner will eventually lap the slow runner—they'll meet at some point
|
|
on the track. This proves the track is circular (has a cycle).
|
|
|
|
Another analogy: finding the middle of a line of people. Have two people start
|
|
at the front—one takes one step at a time, the other takes two. When the fast
|
|
person reaches the end, the slow person is at the middle.
|
|
|
|
core_concept: |
|
|
The **fast & slow pointers** technique (also called Floyd's cycle detection or
|
|
"tortoise and hare") uses two pointers moving at different speeds:
|
|
|
|
- **Slow pointer**: Moves 1 step at a time
|
|
- **Fast pointer**: Moves 2 steps at a time
|
|
|
|
Key insights:
|
|
|
|
1. **Cycle detection**: In a cyclic structure, fast will eventually catch up to
|
|
slow (they'll meet inside the cycle). In a non-cyclic structure, fast will
|
|
reach the end.
|
|
|
|
2. **Finding middle**: When fast reaches the end, slow is at the middle (fast
|
|
traveled 2x the distance).
|
|
|
|
3. **Finding cycle start**: After detecting a cycle, reset one pointer to start.
|
|
Move both at the same speed—they meet at the cycle start. (Mathematical proof:
|
|
the distances work out perfectly.)
|
|
|
|
visualization: |
|
|
**Cycle Detection:**
|
|
|
|
```
|
|
List: 1 → 2 → 3 → 4 → 5
|
|
↑ ↓
|
|
7 ← 6
|
|
|
|
Step 1: slow=1, fast=1
|
|
Step 2: slow=2, fast=3
|
|
Step 3: slow=3, fast=5
|
|
Step 4: slow=4, fast=7
|
|
Step 5: slow=5, fast=4
|
|
Step 6: slow=6, fast=6 ← They meet! Cycle exists.
|
|
```
|
|
|
|
**Finding Middle:**
|
|
|
|
```
|
|
List: 1 → 2 → 3 → 4 → 5 → null
|
|
|
|
Step 1: slow=1, fast=1
|
|
Step 2: slow=2, fast=3
|
|
Step 3: slow=3, fast=5
|
|
Step 4: fast reaches null
|
|
|
|
slow is at middle (3)
|
|
```
|
|
|
|
**Finding Cycle Start:**
|
|
|
|
```
|
|
After detecting cycle at node X:
|
|
|
|
1. Reset slow to head, keep fast at meeting point
|
|
2. Move both at same speed (1 step each)
|
|
3. They meet at cycle start
|
|
|
|
Why? Math: Let's say:
|
|
- Distance from head to cycle start = A
|
|
- Distance from cycle start to meeting point = B
|
|
- Cycle length = C
|
|
|
|
At meeting: slow traveled A + B
|
|
fast traveled A + B + nC (some complete cycles)
|
|
|
|
Since fast travels 2x: 2(A + B) = A + B + nC
|
|
Therefore: A + B = nC, so A = nC - B = (n-1)C + (C-B)
|
|
|
|
This means: distance from head to cycle start
|
|
= distance from meeting point to cycle start (going forward)
|
|
```
|
|
|
|
code_template: |
|
|
class ListNode:
|
|
def __init__(self, val=0, next=None):
|
|
self.val = val
|
|
self.next = next
|
|
|
|
|
|
def has_cycle(head: ListNode) -> bool:
|
|
"""Detect if linked list has a cycle."""
|
|
if not head or not head.next:
|
|
return False
|
|
|
|
slow = head
|
|
fast = head
|
|
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
|
|
if slow == fast:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def find_cycle_start(head: ListNode) -> ListNode:
|
|
"""Find the node where the cycle begins."""
|
|
if not head or not head.next:
|
|
return None
|
|
|
|
# Phase 1: Detect cycle
|
|
slow = fast = head
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
if slow == fast:
|
|
break
|
|
else:
|
|
return None # No cycle
|
|
|
|
# Phase 2: Find cycle start
|
|
slow = head
|
|
while slow != fast:
|
|
slow = slow.next
|
|
fast = fast.next
|
|
|
|
return slow
|
|
|
|
|
|
def find_middle(head: ListNode) -> ListNode:
|
|
"""Find the middle node of linked list."""
|
|
if not head:
|
|
return None
|
|
|
|
slow = fast = head
|
|
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
|
|
return slow # Middle (or second middle if even length)
|
|
|
|
|
|
def is_happy_number(n: int) -> bool:
|
|
"""Check if number is happy (sum of squared digits eventually = 1)."""
|
|
def get_next(num: int) -> int:
|
|
total = 0
|
|
while num > 0:
|
|
digit = num % 10
|
|
total += digit * digit
|
|
num //= 10
|
|
return total
|
|
|
|
slow = n
|
|
fast = get_next(n)
|
|
|
|
while fast != 1 and slow != fast:
|
|
slow = get_next(slow)
|
|
fast = get_next(get_next(fast))
|
|
|
|
return fast == 1
|
|
|
|
|
|
def is_palindrome_linked_list(head: ListNode) -> bool:
|
|
"""Check if linked list is a palindrome."""
|
|
if not head or not head.next:
|
|
return True
|
|
|
|
# Find middle
|
|
slow = fast = head
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
|
|
# Reverse second half
|
|
prev = None
|
|
while slow:
|
|
next_node = slow.next
|
|
slow.next = prev
|
|
prev = slow
|
|
slow = next_node
|
|
|
|
# Compare halves
|
|
left, right = head, prev
|
|
while right:
|
|
if left.val != right.val:
|
|
return False
|
|
left = left.next
|
|
right = right.next
|
|
|
|
return True
|
|
|
|
recognition_signals:
|
|
- "linked list cycle"
|
|
- "detect cycle"
|
|
- "find middle"
|
|
- "happy number"
|
|
- "palindrome linked list"
|
|
- "circular array"
|
|
- "Floyd's"
|
|
- "tortoise and hare"
|
|
- "meeting point"
|
|
- "cycle start"
|
|
|
|
common_mistakes:
|
|
- title: Not checking fast.next before advancing
|
|
description: |
|
|
Accessing `fast.next.next` without first checking `fast.next` causes
|
|
null pointer errors when the list has even length.
|
|
fix: |
|
|
Always check both `fast` and `fast.next`:
|
|
```python
|
|
while fast and fast.next:
|
|
fast = fast.next.next
|
|
```
|
|
|
|
- title: Wrong initialization for cycle detection
|
|
description: |
|
|
Starting slow and fast at different positions (e.g., slow=head, fast=head.next)
|
|
changes the math for finding the cycle start.
|
|
fix: |
|
|
Start both at head for consistency. The algorithms are designed assuming
|
|
both start at the same position.
|
|
|
|
- title: Forgetting to handle empty or single-node lists
|
|
description: |
|
|
Accessing head.next or head.next.next on empty or single-node lists
|
|
causes errors.
|
|
fix: |
|
|
Add early returns:
|
|
```python
|
|
if not head or not head.next:
|
|
return False # or appropriate value
|
|
```
|
|
|
|
- title: Confusing meeting point with cycle start
|
|
description: |
|
|
Returning the meeting point instead of finding the actual cycle start
|
|
gives the wrong answer.
|
|
fix: |
|
|
After detecting a cycle (meeting point), reset one pointer to head and
|
|
advance both at the same speed to find the cycle start.
|
|
|
|
variations:
|
|
- name: Cycle detection
|
|
description: |
|
|
Determine if a linked list or sequence has a cycle. If fast catches slow,
|
|
there's a cycle.
|
|
example: "Linked List Cycle, Circular Array Loop"
|
|
|
|
- name: Finding cycle start
|
|
description: |
|
|
After detecting a cycle, find the node where the cycle begins using the
|
|
two-phase approach.
|
|
example: "Linked List Cycle II"
|
|
|
|
- name: Finding middle
|
|
description: |
|
|
When fast reaches the end, slow is at the middle. Useful for divide and
|
|
conquer on linked lists.
|
|
example: "Middle of Linked List, Sort List (merge sort needs middle)"
|
|
|
|
- name: Happy number
|
|
description: |
|
|
Treat the sequence of digit-square sums as a linked list. Either reaches 1
|
|
(happy) or cycles (unhappy).
|
|
example: "Happy Number"
|
|
|
|
- name: Palindrome check
|
|
description: |
|
|
Find middle, reverse second half, compare. Combines finding middle with
|
|
linked list reversal.
|
|
example: "Palindrome Linked List"
|
|
|
|
related_patterns:
|
|
- two-pointers
|
|
- linkedlist-reversal
|
|
|
|
prerequisite_patterns:
|
|
- two-pointers
|
|
|
|
visualization_examples:
|
|
- id: find-middle-of-list
|
|
title: Find Middle of Linked List
|
|
input:
|
|
list: [1, 2, 3, 4, 5]
|
|
code: |
|
|
def find_middle(head):
|
|
slow = fast = head
|
|
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
|
|
return slow
|
|
steps:
|
|
- id: step-1
|
|
description: "Initialize slow and fast pointers at head (node 1)."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: active }
|
|
- { value: 2, state: default }
|
|
- { value: 3, state: default }
|
|
- { value: 4, state: default }
|
|
- { value: 5, state: default }
|
|
pointers:
|
|
slow: 0
|
|
fast: 0
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 2
|
|
endLine: 2
|
|
|
|
- id: step-2
|
|
description: "Move slow 1 step (to 2), fast 2 steps (to 3)."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: active }
|
|
- { value: 3, state: active }
|
|
- { value: 4, state: default }
|
|
- { value: 5, state: default }
|
|
pointers:
|
|
slow: 1
|
|
fast: 2
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 5
|
|
endLine: 6
|
|
|
|
- id: step-3
|
|
description: "Move slow 1 step (to 3), fast 2 steps (to 5)."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: visited }
|
|
- { value: 3, state: active }
|
|
- { value: 4, state: visited }
|
|
- { value: 5, state: active }
|
|
pointers:
|
|
slow: 2
|
|
fast: 4
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 5
|
|
endLine: 6
|
|
|
|
- id: step-4
|
|
description: "fast.next is null, loop ends. slow is at middle (node 3)."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: visited }
|
|
- { value: 3, state: found }
|
|
- { value: 4, state: visited }
|
|
- { value: 5, state: visited }
|
|
pointers:
|
|
slow: 2
|
|
variables:
|
|
result: 3
|
|
codeHighlight:
|
|
startLine: 8
|
|
endLine: 8
|
|
|
|
- id: detect-cycle
|
|
title: Detect Cycle in Linked List
|
|
input:
|
|
list: [1, 2, 3, 4, 5]
|
|
cycle_at: 2
|
|
code: |
|
|
def has_cycle(head):
|
|
slow = fast = head
|
|
|
|
while fast and fast.next:
|
|
slow = slow.next
|
|
fast = fast.next.next
|
|
|
|
if slow == fast:
|
|
return True
|
|
|
|
return False
|
|
steps:
|
|
- id: step-1
|
|
description: "List has a cycle: 5 points back to 3. Initialize pointers at head."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: active }
|
|
- { value: 2, state: default }
|
|
- { value: 3, state: default, annotations: ["cycle start"] }
|
|
- { value: 4, state: default }
|
|
- { value: 5, state: default, annotations: ["→3"] }
|
|
pointers:
|
|
slow: 0
|
|
fast: 0
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 2
|
|
endLine: 2
|
|
|
|
- id: step-2
|
|
description: "slow moves to 2, fast moves to 3."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: active }
|
|
- { value: 3, state: active, annotations: ["cycle start"] }
|
|
- { value: 4, state: default }
|
|
- { value: 5, state: default, annotations: ["→3"] }
|
|
pointers:
|
|
slow: 1
|
|
fast: 2
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 5
|
|
endLine: 6
|
|
|
|
- id: step-3
|
|
description: "slow moves to 3, fast moves to 5."
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: visited }
|
|
- { value: 3, state: active, annotations: ["cycle start"] }
|
|
- { value: 4, state: visited }
|
|
- { value: 5, state: active, annotations: ["→3"] }
|
|
pointers:
|
|
slow: 2
|
|
fast: 4
|
|
variables: {}
|
|
codeHighlight:
|
|
startLine: 5
|
|
endLine: 6
|
|
|
|
- id: step-4
|
|
description: "slow moves to 4, fast loops back to 4 (via 5→3→4). They meet!"
|
|
structures:
|
|
list:
|
|
type: linkedlist
|
|
nodes:
|
|
- { value: 1, state: visited }
|
|
- { value: 2, state: visited }
|
|
- { value: 3, state: visited, annotations: ["cycle start"] }
|
|
- { value: 4, state: found, annotations: ["meeting point"] }
|
|
- { value: 5, state: visited, annotations: ["→3"] }
|
|
pointers:
|
|
"slow, fast": 3
|
|
variables:
|
|
cycle_detected: true
|
|
codeHighlight:
|
|
startLine: 8
|
|
endLine: 9
|