Fast and Slow Pointers (Floyd's Cycle Detection)
1. Cycle Detection in Linked Lists
| Algorithm | Approach | Time | Space |
|---|---|---|---|
| Floyd's Algorithm | Fast moves 2 steps, slow moves 1 step | O(n) |
O(1) |
| HashSet Method | Store visited nodes in set | O(n) |
O(n) |
| Collision Point | If cycle exists, fast catches slow | Within cycle length | Meeting point inside cycle |
| Why It Works | Distance closes by 1 each iteration in cycle | Guaranteed meeting | Fast gains 1 step per iteration on slow |
Example: Floyd's cycle detection algorithm
function hasCycle(head) {
if (!head || !head.next) return false;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next; // Move 1 step
fast = fast.next.next; // Move 2 steps
if (slow === fast) {
return true; // Cycle detected
}
}
return false; // No cycle
}
// Time: O(n), Space: O(1)
// If cycle exists, they meet in at most n steps
Example: Alternative with HashSet (for comparison)
function hasCycleHashSet(head) {
const visited = new Set();
let current = head;
while (current) {
if (visited.has(current)) {
return true; // Found cycle
}
visited.add(current);
current = current.next;
}
return false;
}
// Time: O(n), Space: O(n) - less efficient than Floyd's
2. Find Middle of Linked List
| Scenario | Technique | Result | Use Case |
|---|---|---|---|
| Odd Length List | When fast reaches end, slow at middle | Exact middle node | 1→2→3→4→5, middle = 3 |
| Even Length List | Slow at second middle | Right of two middle nodes | 1→2→3→4, middle = 3 |
| Single Node | Return head | Head itself is middle | Edge case handling |
| Two Nodes | Return second node | Fast reaches end, slow at 2nd | 1→2, middle = 2 |
3. Palindrome Linked List Verification
| Step | Operation | Complexity | Purpose |
|---|---|---|---|
| 1. Find Middle | Use fast-slow pointers | O(n/2) |
Locate midpoint of list |
| 2. Reverse Second Half | Reverse from middle to end | O(n/2) |
Prepare for comparison |
| 3. Compare Halves | Compare first half with reversed second | O(n/2) |
Check if values match |
| 4. Restore (Optional) | Reverse second half back to original | O(n/2) |
Restore input list structure |
Example: Check if linked list is palindrome
function isPalindrome(head) {
if (!head || !head.next) return true;
// Step 1: Find middle using fast-slow pointers
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
// Step 2: Reverse second half
let prev = null;
while (slow) {
const next = slow.next;
slow.next = prev;
prev = slow;
slow = next;
}
// Step 3: Compare first and reversed second half
let left = head;
let right = prev;
while (right) { // Right is shorter or equal
if (left.val !== right.val) {
return false;
}
left = left.next;
right = right.next;
}
return true;
}
// 1→2→2→1 -> true
// 1→2→3→2→1 -> true
// 1→2→3 -> false
4. Intersection of Two Linked Lists
| Approach | Strategy | Complexity | Key Insight |
|---|---|---|---|
| Two Pointers Sync | Traverse both lists, switch at end | O(m+n), O(1) |
Equal path length after switching |
| Length Difference | Calculate lengths, align starting points | O(m+n), O(1) |
Advance longer list by difference |
| HashSet | Store nodes from one list, check other | O(m+n), O(m or n) |
Trade space for simplicity |
| Path Equalization | A→B + B→A = same length as B→A + A→B | Meet at intersection | Both traverse combined length |
Example: Find intersection node (elegant two-pointer)
function getIntersectionNode(headA, headB) {
if (!headA || !headB) return null;
let pA = headA;
let pB = headB;
// Traverse both lists
// When reaching end, switch to other list's head
while (pA !== pB) {
pA = pA ? pA.next : headB;
pB = pB ? pB.next : headA;
}
return pA; // Either intersection node or null
}
// Why it works:
// If intersection exists:
// pA: a1→a2→c1→c2→c3→(switch)→b1→b2→b3→c1 (meet here)
// pB: b1→b2→b3→c1→c2→c3→(switch)→a1→a2→c1 (meet here)
// If no intersection:
// Both reach null at same time
Example: Intersection with length calculation
function getIntersectionNodeByLength(headA, headB) {
const getLength = (head) => {
let len = 0;
while (head) {
len++;
head = head.next;
}
return len;
};
let lenA = getLength(headA);
let lenB = getLength(headB);
// Align starting points
while (lenA > lenB) {
headA = headA.next;
lenA--;
}
while (lenB > lenA) {
headB = headB.next;
lenB--;
}
// Move together until intersection
while (headA !== headB) {
headA = headA.next;
headB = headB.next;
}
return headA;
}
5. Duplicate Detection in Arrays O(1) Space
| Constraint | Technique | Complexity | Problem Type |
|---|---|---|---|
| Array [1, n] | Treat as linked list, values as indices | O(n), O(1) |
Find duplicate in array of n+1 elements |
| Floyd's in Array | slow = arr[slow], fast = arr[arr[fast]] | O(n), O(1) |
Cycle detection where duplicate creates cycle |
| No Constraints | HashSet to track seen elements | O(n), O(n) |
General duplicate detection |
| Sorted Array | Adjacent comparison or binary search | O(n) or O(log n) |
Duplicates are adjacent |
Example: Find duplicate number using Floyd's algorithm
function findDuplicate(nums) {
// Treat array as linked list: index -> value
// Duplicate creates a cycle
// Phase 1: Detect cycle
let slow = nums[0];
let fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// Phase 2: Find cycle entrance (duplicate)
slow = nums[0];
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
// nums = [1,3,4,2,2]
// Index: 0→1, 1→3, 2→4, 3→2, 4→2 (cycle: 2→4→2)
// Output: 2
Note: The duplicate detection works because if nums[i] = nums[j] where i ≠ j, then following the "linked list" indices leads to a cycle. The entrance to this cycle is the duplicate value.
6. Find Starting Point of Cycle
| Phase | Action | Mathematical Basis | Result |
|---|---|---|---|
| Phase 1: Detection | Fast-slow meet inside cycle | Distance from start to meeting = k × cycle_length | Confirm cycle exists |
| Phase 2: Find Start | Reset slow to head, both move at same speed | Distance from head to start = distance from meet to start | They meet at cycle start |
| Proof | Let L = dist to cycle, C = cycle length | When they meet: 2×slow = fast = L + kC | Remaining distance = L |
| Cycle Length | Keep one pointer fixed, count steps | Count iterations until pointers meet again | Optional: determine cycle size |
Example: Find cycle start position
function detectCycleStart(head) {
let slow = head;
let fast = head;
// Phase 1: Detect if cycle exists
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
// Cycle detected, find start
// Phase 2: Find cycle entrance
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow; // Cycle start
}
}
return null; // No cycle
}
// Mathematical proof:
// Let L = distance from head to cycle start
// Let C = cycle length
// Let X = distance from cycle start to meeting point
// When they meet: Fast traveled 2×distance of Slow
// Fast: L + nC + X (n loops in cycle)
// Slow: L + X
// Therefore: 2(L + X) = L + nC + X
// Simplify: L = nC - X
// So distance from head to start = (cycle length - meeting distance)
Example: Find cycle length
function getCycleLength(head) {
let slow = head;
let fast = head;
// Detect cycle
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
// Found cycle, now count length
let count = 1;
let current = slow.next;
while (current !== slow) {
count++;
current = current.next;
}
return count;
}
}
return 0; // No cycle
}
Note: Fast-slow pointer pattern achieves O(1) space complexity for problems that would otherwise require O(n) space with HashSet. The key insight is using speed differential to detect patterns like cycles and middle elements.
Warning: Always check both
fast and fast.next before accessing fast.next.next to prevent null pointer errors. For array-based Floyd's, ensure array values are valid indices to avoid out-of-bounds access.