Fast & Slow Pointers
Floyd's tortoise and hare: two pointers walk the same list at different speeds — one hop vs. two. If there's a cycle, the fast one must lap the slow one; if there isn't, fast hits the end when slow is at the middle. Cycle detection, midpoints, and duplicates in O(1) space.
The hare must catch the tortoise
Slow (the tortoise) moves 1 node per round; fast (the hare) moves 2. Once both pointers are inside a cycle, fast gains exactly one node on slow per round — so the gap between them shrinks to zero and they must meet, within one lap. If there is no cycle, fast simply runs off the end. No hash set, no marking nodes: two variables, one pass.
Why phase 2 lands on the entry
Name three distances: D from the head to the cycle entry,
C the cycle length, and K from the entry to the
meeting point.
Read the last line out loud: the distance from the head to the entry equals the distance from the meeting point to the entry, plus some whole number of laps. So put one pointer at the head and one at the meeting point, march both at speed 1, and they collide exactly at the cycle entry. That's the two-phase algorithm of LC 142 — and, remarkably, of LC 287.
When to reach for it
| signal in the problem | technique |
|---|---|
| "Does this linked list have a cycle?" | Fast/slow — check if they meet |
| "Where does the cycle start?" | Phase 2 — reset one pointer to head |
| "Find the middle of the list" | When fast hits the end, slow = middle |
| "Is this a happy number?" | Cycle detection on a number sequence |
| "Find the duplicate in [1..n]" | Treat the array as an implicit linked list |
| "Is the list a palindrome?" | Find middle + reverse + compare |
| "Reorder the list" | Find middle + reverse + merge |
The deeper signal: you're walking a structure where each element has exactly
one successor — node.next, nums[i],
get_next(n) — and you care about cycles or midpoints while being held
to O(1) extra space.
The three templates to memorize
T1Cycle detection — LC 141
def has_cycle(head):
"""Returns True if the linked list has a cycle."""
slow = fast = head
while fast and fast.next:
slow = slow.next # 1 step
fast = fast.next.next # 2 steps
if slow == fast:
return True
return False # fast reached the end -> no cycle
# Why check "fast and fast.next"?
# - fast could be None (even-length list: fast stepped past the end)
# - fast.next could be None (odd-length list: fast is on the last node)
T2Find the cycle start (two-phase) — LC 142
def find_cycle_start(head):
"""Returns the node where the cycle begins, or None."""
# Phase 1: detect the cycle (find the meeting point)
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 the entry point
# Reset one pointer to head; BOTH move at speed 1
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow # the cycle entry
T3Middle of the list — LC 876
def middleNode(head):
"""Return the middle node. For even length, the 2nd middle."""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# To get the FIRST middle of an even-length list, stop earlier:
def middleNode_first(head):
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
return slow # for [1,2,3,4,5,6]: node 3, not node 4
while fast and fast.next on 1→…→6. When fast exhausts the list, slow is halfway.while fast and fast.next → 2nd middle (node 4 in [1,2,3,4,5,6]).
while fast.next and fast.next.next → 1st middle (node 3).
For odd-length lists both stop on the exact middle. Choose per problem —
and say which one you chose in the interview.
The same trick in four disguises
A1Happy Number — LC 202
No linked list in sight — but get_next gives every number exactly one
successor, so the sequence is an implicit list. Unhappy numbers fall into a
cycle; fast/slow detects it without a hash set.
def isHappy(n):
"""Happy if the sum-of-squared-digits sequence reaches 1."""
def get_next(num):
total = 0
while num > 0:
num, digit = divmod(num, 10)
total += digit * digit
return total
slow = n
fast = get_next(n)
while fast != 1 and slow != fast:
slow = get_next(slow) # 1 step
fast = get_next(get_next(fast)) # 2 steps
return fast == 1
# n = 19: 19 -> 82 -> 68 -> 100 -> 1 HAPPY
# n = 2: 2 -> 4 -> 16 -> 37 -> 58 -> 89
# -> 145 -> 42 -> 20 -> 4 -> ... cycle -> not happy
A2Find the Duplicate Number — LC 287
An array of n + 1 integers in range [1, n] has exactly one repeated
value. Treat index → nums[index] as a next-pointer: the duplicate value
is pointed at by two indices, which creates a cycle whose entry is the
duplicate. Full two-phase Floyd, zero extra space.
def findDuplicate(nums):
"""
Array of n+1 integers in range [1, n]. Exactly one duplicate.
Treat the array as a linked list: index -> value = next pointer.
The duplicate creates a cycle; cycle entry = duplicate value.
"""
# Phase 1: find the meeting point
slow = fast = 0 # start at index 0 (the "head")
while True:
slow = nums[slow] # one hop
fast = nums[nums[fast]] # two hops
if slow == fast:
break
# Phase 2: find the cycle entry
slow = 0
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow # the duplicate number
A3Palindrome Linked List — LC 234
def isPalindrome(head):
"""Check if a linked list is a palindrome. O(n) time, O(1) space."""
if not head or not head.next:
return True
# Step 1: find the middle (first middle for even length)
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Step 2: reverse the second half
prev = None
curr = slow.next
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# prev is now the head of the reversed second half
# Step 3: compare front half with reversed back half
left, right = head, prev
while right: # the right half is shorter or equal
if left.val != right.val:
return False
left = left.next
right = right.next
return True
# 1 -> 2 -> 3 -> 2 -> 1
# middle = 3; reverse after it: second half becomes 1 -> 2
# compare 1,2 with 1,2 -> palindrome
A4Reorder List — LC 143
def reorderList(head):
"""Reorder L0 -> L1 -> ... -> Ln to L0 -> Ln -> L1 -> Ln-1 -> ..."""
if not head or not head.next:
return
# Step 1: find the middle
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Step 2: reverse the second half
prev = None
curr = slow.next
slow.next = None # cut the list in two
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Step 3: interleave the two halves
first, second = head, prev
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first, second = tmp1, tmp2
# 1 -> 2 -> 3 -> 4 -> 5
# split: 1 -> 2 -> 3 and 4 -> 5
# reverse: 1 -> 2 -> 3 and 5 -> 4
# merge: 1 -> 5 -> 2 -> 4 -> 3
| problem | time | space | notes |
|---|---|---|---|
| Linked List Cycle | O(n) | O(1) | Fast catches slow within one lap |
| Linked List Cycle II | O(n) | O(1) | Two phases, each O(n) |
| Middle of Linked List | O(n) | O(1) | Slow traverses half the list |
| Happy Number | O(log n) | O(1) | Digit processing is O(log n) |
| Find Duplicate Number | O(n) | O(1) | No extra space needed |
| Palindrome Linked List | O(n) | O(1) | Find mid + reverse + compare |
| Reorder List | O(n) | O(1) | Find mid + reverse + merge |
Where the hare trips
fast and fast.next before advancing.
while fast.next: alone crashes when fast is already None
(even-length list).
while fast and fast.next: # CORRECT
while fast.next: # WRONG - fast could be None
while fast and fast.next gives the 2nd
(node 4 in [1,2,3,4,5,6]); while fast.next and fast.next.next gives the
1st (node 3). Pick the one the problem needs.
D = (n−1)C + (C−K) math only works at equal speed.
0 (the "head"), not index 1. Values live in [1, n],
so no value ever points back to index 0 — it's guaranteed outside the cycle.
slow.next = None to actually
split the list into two halves before reversing.
Practice ladder
In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.