Fast and Slow Pointers
Introduction
The Fast and Slow Pointers pattern (also known as the "Hare and Tortoise" algorithm) is a pointer technique that uses two pointers moving through a sequence at different speeds. This elegant approach is particularly useful for:
- Detecting cycles in a linked list
- Finding the middle of a linked list
- Determining if a linked list is a palindrome
- Finding a specific element in a circular array
The beauty of this technique lies in its simplicity and efficiency. By using two pointers that move at different speeds, we can solve problems with O(n) time complexity and O(1) space complexity.
How It Works
The core idea is straightforward:
- Initialize two pointers at the beginning of a sequence
- Move the "slow" pointer one step at a time
- Move the "fast" pointer two (or more) steps at a time
- Analyze what happens based on how these pointers move and potentially intersect
Let's visualize this pattern:
In the standard implementation:
- The slow pointer starts at the head (node 1)
- The fast pointer also starts at the head (node 1)
After one iteration:
- Slow moves to node 2
- Fast moves to node 3
Common Applications
1. Detecting Cycles in a Linked List
One of the most well-known applications is detecting if a linked list has a cycle.
function hasCycle(head) {
if (!head || !head.next) return false;
let slow = head;
let fast = head;
// Move slow by 1 and fast by 2
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
// If they meet, there's a cycle
if (slow === fast) {
return true;
}
}
return false;
}
How it works:
- In a linked list without a cycle, the fast pointer will eventually reach the end
- In a linked list with a cycle, the fast pointer will eventually catch up to the slow pointer
Example:
Input: 1 → 2 → 3 → 4 → 2 (cycles back to 2)
Output: true
Input: 1 → 2 → 3 → 4 → null
Output: false
2. Finding the Middle of a Linked List
Finding the middle element efficiently in a single pass:
function findMiddle(head) {
if (!head) return null;
let slow = head;
let fast = head;
// When fast reaches the end, slow will be at the middle
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
Example:
Input: 1 → 2 → 3 → 4 → 5 → null
Output: 3 (the middle node)
Input: 1 → 2 → 3 → 4 → null
Output: 3 (for even length lists, we return the second middle node)
3. Finding the Start of a Cycle
Once we've detected a cycle, we can find where it begins:
function detectCycleStart(head) {
if (!head || !head.next) return null;
let slow = head;
let fast = head;
let hasCycle = false;
// Detect if there's a cycle
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
hasCycle = true;
break;
}
}
if (!hasCycle) return null;
// Find the start of the cycle
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
How it works:
- First, we detect if there's a cycle using the standard algorithm
- Then, we reset the slow pointer to head
- Move both pointers at the same pace (one step each)
- The point where they meet is the start of the cycle
4. Determining If a Linked List is a Palindrome
We can use fast and slow pointers to efficiently check if a linked list is a palindrome:
function isPalindrome(head) {
if (!head || !head.next) return true;
// Find the middle of the linked list
let slow = head;
let fast = head;
while (fast.next !== null && fast.next.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
// Reverse the second half
let secondHalfHead = reverseList(slow.next);
let firstHalfPtr = head;
let secondHalfPtr = secondHalfHead;
// Compare the two halves
let result = true;
while (secondHalfPtr !== null) {
if (firstHalfPtr.val !== secondHalfPtr.val) {
result = false;
break;
}
firstHalfPtr = firstHalfPtr.next;
secondHalfPtr = secondHalfPtr.next;
}
// Restore the list (optional)
slow.next = reverseList(secondHalfHead);
return result;
}
function reverseList(head) {
let prev = null;
let current = head;
while (current !== null) {
let next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}
Challenge: Find the Happy Number
A "happy number" is defined by the following process:
- Starting with any positive integer, replace the number by the sum of the squares of its digits
- Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle
- Those numbers for which this process ends in 1 are happy numbers
We can solve this using fast and slow pointers:
function isHappy(n) {
let slow = n;
let fast = n;
do {
slow = calculateSquareSum(slow);
fast = calculateSquareSum(calculateSquareSum(fast));
} while (slow !== fast);
return slow === 1;
}
function calculateSquareSum(n) {
let sum = 0;
while (n > 0) {
let digit = n % 10;
sum += digit * digit;
n = Math.floor(n / 10);
}
return sum;
}
Example:
Input: 19
Output: true
Explanation:
1² + 9² = 82
8² + 2² = 68
6² + 8² = 100
1² + 0² + 0² = 1
When to Use Fast and Slow Pointers
Use this pattern when:
- Dealing with cyclic linked lists or arrays
- You need to find a specific position in a linked list (like the middle element)
- Problems involving cycle detection
- You need O(1) space complexity
Common Variations
- Different starting positions: Sometimes the fast pointer starts one node ahead
- Different speeds: Though commonly the fast pointer moves twice as fast as the slow one, this ratio can be adjusted
- More than two pointers: Some complex problems might require three or more pointers moving at different speeds
Summary
The Fast and Slow Pointers technique is an elegant solution for many linked list problems, offering optimal space complexity. It's especially powerful for:
- Cycle detection
- Finding the middle element
- Finding the start of a cycle
- Checking for palindromes
- Solving certain mathematical problems like detecting happy numbers
The key insight is that by having pointers move at different speeds, we can infer properties about the data structure based on if, when, and where they meet.
Practice Exercises
- Find if a linked list has a cycle
- Find the length of a cycle in a linked list
- Find the middle node of a linked list
- Determine if a linked list is a palindrome
- Find the node where two linked lists intersect
- Implement the "happy number" algorithm
Additional Resources
- Floyd's Cycle-Finding Algorithm (the mathematical foundation)
- Tortoise and Hare algorithm variations
- Problems involving cyclical detection in arrays
Remember, the Fast and Slow Pointers pattern is a fundamental technique that every programmer should master. It's a perfect example of how a simple idea can lead to elegant and efficient solutions.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)