Using Fast and Slow Pointers
1. Understanding Floyd's Cycle Detection
| Aspect | Description | Details |
|---|---|---|
| Algorithm Name | Used For | Detecting cycles in linked lists |
| Two Pointers | Approach | Slow pointer moves one step, fast pointer moves two steps |
| Cycle Detection | Logic | If there's a cycle, fast pointer will eventually catch up to slow pointer |
| Time Complexity | Best Case | O(n) |
| Space Complexity | Best Case | O(1) |
Example: Detecting a Cycle in a Linked List
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function hasCycle(head) {
if (!head || !head.next) return false;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
// Example usage:
const node1 = new ListNode(3);
const node2 = new ListNode(2);
const node3 = new ListNode(0);
const node4 = new ListNode(-4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node2; // Creates a cycle
console.log(hasCycle(node1)); // true
Note: Floyd's cycle detection is also known as the "tortoise and hare" algorithm.
2. Detecting Cycle in LinkedList
| Approach | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| Fast and Slow Pointers | Use two pointers moving at different speeds. If there's a cycle, they will eventually meet. | O(n) |
O(1) |
Example: Detecting Cycle in a LinkedList
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true; // Cycle detected
}
}
return false; // No cycle
}
// Example usage:
const node1 = new ListNode(3);
const node2 = new ListNode(2);
const node3 = new ListNode(0);
const node4 = new ListNode(-4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node2; // Creates a cycle
console.log(hasCycle(node1)); // true
Note: The fast pointer moves twice as fast as the slow pointer. If there is a cycle, they will meet at some point inside the cycle.
3. Finding Middle of LinkedList
| Property | Description | Usage |
|---|---|---|
| fastPointer | Traverses twice as fast as slow pointer | Used to detect end of list or find middle |
| slowPointer | Traverses one node at a time | Used to find middle element when fast pointer reaches end |
Example: Finding Middle Node
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function findMiddle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow.val;
}
// Example usage:
const list = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4))));
console.log(findMiddle(list)); // Output: 3
Note: When the fast pointer reaches the end of the list, the slow pointer will be at the middle node.
4. Checking Palindrome LinkedList
| Property | Description | Usage |
|---|---|---|
| isPalindrome(head) | Checks if a linked list is a palindrome using fast/slow pointers | Returns true if the list is a palindrome, otherwise false |
Example: isPalindrome
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function isPalindrome(head) {
if (!head || !head.next) return true;
let slow = head;
let fast = head;
// Find the middle of the list
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
// Reverse the second half
let prev = null;
let current = slow;
while (current) {
const next = current.next;
current.next = prev;
prev = current;
current = next;
}
// Compare first and second half
let left = head;
let right = prev;
while (right) {
if (left.val !== right.val) return false;
left = left.next;
right = right.next;
}
return true;
}
// Example usage:
const list1 = new ListNode(1, new ListNode(2, new ListNode(2, new ListNode(1))));
console.log(isPalindrome(list1)); // true
const list2 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4))));
console.log(isPalindrome(list2)); // false
Note: This approach uses O(1) extra space and O(n) time complexity.
5. Finding 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 which does not include 1.
- If the process ends in 1, the number is happy.
Use the fast and slow pointer technique to detect cycles in this process.
| Property | Description | Example |
|---|---|---|
| Input | Positive integer | 19 |
| Process | Sum of squares of digits | 1² + 9² = 1 + 81 = 82 |
| Cycle Detection | Use fast and slow pointers to detect loops | Same as in linked list cycle detection |
Example: Happy Number Check
function isHappy(n) {
function getSquareSum(num) {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}
let slow = n;
let fast = n;
do {
slow = getSquareSum(slow);
fast = getSquareSum(getSquareSum(fast));
} while (slow !== fast && slow !== 1);
return slow === 1;
}
// Example usage:
console.log(isHappy(19)); // true
console.log(isHappy(2)); // false
Note: The fast pointer moves two steps at a time, while the slow pointer moves one step. If there's a cycle, they will eventually meet.
6. Finding Cycle Start Node
When a cycle exists in a linked list, the fast and slow pointers will eventually meet. Once they meet, we can find the start of the cycle using a specific technique.
| Step | Description | Action |
|---|---|---|
| 1 | Move one pointer to the head of the list | Reset slow to the head, keep fast at the meeting point |
| 2 | Advance both pointers at same speed | Move slow and fast one step at a time until they meet again |
| 3 | Meeting point is the cycle start | The node where they meet is the beginning of the cycle |
Example: Finding the Start of a Cycle in a Linked List
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function findCycleStart(head) {
let slow = head;
let fast = head;
// Phase 1: Detect if there's a cycle
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
break;
}
}
// If no cycle exists, return null
if (!fast || !fast.next) {
return null;
}
// Phase 2: Find the start of the cycle
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow; // Start of the cycle
}
// Example usage:
const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);
const node4 = new ListNode(4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node2; // Creates a cycle
console.log(findCycleStart(node1).val); // Output: 2
Note: The algorithm works because when the two pointers meet inside the cycle, the distance from the head to the start of the cycle equals the distance from the meeting point to the start of the cycle, when traveling around the cycle once.
7. Rearranging LinkedList
| Operation | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| Reorder List | Reorders a singly linked list to alternate between first and last nodes. | O(n) |
O(1) |
| Reverse Sublist | Reverses a portion of the list between given positions. | O(n) |
O(1) |
| Remove Duplicates | Removes duplicates from a sorted list in-place. | O(n) |
O(1) |
Example: Reorder List
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function reorderList(head) {
if (!head || !head.next) return;
// Step 1: Find the middle of the list
let slow = head, fast = head;
while (fast.next && fast.next.next) {
slow = slow.next;
fast = fast.next.next;
}
// Step 2: Reverse the second half
let prev = null, curr = slow.next;
slow.next = null; // Break the list
while (curr) {
let next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
// Step 3: Merge the two halves
let first = head, second = prev;
while (second) {
let next1 = first.next, next2 = second.next;
first.next = second;
second.next = next1;
first = next1;
second = next2;
}
}
// Example usage:
const list = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4))));
reorderList(list);
// Result: [1, 4, 2, 3]
Example: Remove Duplicates
function removeDuplicates(head) {
if (!head) return null;
let current = head;
while (current && current.next) {
if (current.val === current.next.val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}
// Example usage:
const list = new ListNode(1, new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(3)))));
const result = removeDuplicates(list);
// Result: [1, 2, 3]
8. Finding Duplicate Number
| Approach | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| Floyd's Cycle Detection | Uses two pointers moving at different speeds to detect a cycle in the array, treating it as a linked list where each value points to an index. | O(n) |
O(1) |
Example: Finding Duplicate Number Using Floyd's Cycle Detection
function findDuplicate(nums) {
// 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 entrance to cycle
slow = nums[0];
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
// Example usage:
const nums = [1, 3, 4, 2, 2];
console.log(findDuplicate(nums)); // Output: 2
Note: This approach treats the array as a linked list where each value points to an index, and uses Floyd's cycle detection algorithm to find the duplicate.
9. Removing Nth Node from End
| Property | Description | Usage |
|---|---|---|
| fast/slow pointers | Two pointers moving at different speeds to traverse list | Use to find nth node from end |
| distance between pointers | Fixed gap to maintain between fast and slow pointer | Set to n steps ahead |
Example: Remove Nth Node from End
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
function removeNthFromEnd(head, n) {
let fast = head;
let slow = head;
// Move fast pointer n steps ahead
for (let i = 0; i < n; i++) {
fast = fast.next;
}
// If fast is null, remove head node
if (!fast) {
return head.next;
}
// Move both pointers until fast reaches end
while (fast.next) {
fast = fast.next;
slow = slow.next;
}
// Remove the nth node from end
slow.next = slow.next.next;
return head;
}
// Example usage:
const list = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
const result = removeNthFromEnd(list, 2); // Removes 4
Note: This approach uses two pointers to find the node before the target in one pass.
10. Detecting Intersection of Lists
| Property | Description | Usage |
|---|---|---|
| Intersection Detection | Detects if two linked lists intersect at a common node. | Used in list manipulation and memory management. |
Example: Detecting List Intersection
class ListNode {
constructor(val) {
this.val = val;
this.next = null;
}
}
function getIntersectionNode(headA, headB) {
if (!headA || !headB) return null;
let pointerA = headA;
let pointerB = headB;
while (pointerA !== pointerB) {
pointerA = pointerA ? pointerA.next : headB;
pointerB = pointerB ? pointerB.next : headA;
}
return pointerA; // Intersection node or null
}
// Example usage:
const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);
const node4 = new ListNode(4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
const node5 = new ListNode(5);
const node6 = new ListNode(6);
node5.next = node6;
node6.next = node3; // Intersection at node3
console.log(getIntersectionNode(node1, node5).val); // 3
Note: The fast and slow pointer technique is not directly used here, but the two-pointer approach efficiently detects intersections by traversing both lists.