02. Fast and Slow Pointers#
Theory#
Description#
The Fast and Slow Pointer technique, also called the Tortoise and Hare algorithm, is a powerful method for efficiently solving problems in linked lists and cyclic structures.
How It Works#
- The slow pointer moves one step at a time.
- The fast pointer moves two steps at a time.
The difference in speeds allows the fast pointer to catch up with the slow pointer if there's a cycle or to identify the middle of a structure.
Key Insights#
- Cycle Detection: If a cycle exists, the fast pointer will meet the slow pointer inside the cycle.
- Middle Element: The slow pointer will be at the middle when the fast pointer reaches the end.
- Pattern Matching: Helps detect patterns like palindromes by dividing the structure into two parts.
Benefits#
- Time Efficient: Solves problems in O(n) time with a single traversal.
- Space Efficient: Requires O(1) space, avoiding extra data structures.
- Simple & Elegant: Reduces complex problems to simple solutions.
Problems at a glance#
| LC | Problem | |
|---|---|---|
| 141 | 1. Linked List Cycle | ↗ |
| 142 | 2. Linked List Cycle II | ↗ |
| 202 | 3. Happy Number | ↗ |
| 876 | 4. Middle of the Linked List | ↗ |
| 234 | 5. Palindrome Linked List | ↗ |
| 143 | 6. Reorder List | ↗ |
| 457 | 7. Circular Array Loop | ↗ |
| 19 | 8. Remove Nth Node From End of List | ↗ |
| 287 | 9. Find the Duplicate Number | ↗ |
Problems#
1. Linked List Cycle (Leetcode:141)#
Problem Statement
Given head, the head of a linked list, determine if the linked list has a cycle in it.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter.
Return true if there is a cycle in the linked list. Otherwise, return false.
Example 1:
Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed).
Example 2:
Input: head = [1,2], pos = 0
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 0th node.
Example 3:
Input: head = [1], pos = -1
Output: false
Explanation: There is no cycle in the linked list.
Constraints:
The number of the nodes in the list is in the range [0, 10^4].
-10^5 <= Node.val <= 10^5
pos is -1 or a valid index in the linked-list.
Follow up:
Can you solve it using O(1) (i.e. constant) memory?
Code and Explanation
- Initialize pointers: Set both
slowandfasttohead. - Move at different speeds: Advance
slowone node andfasttwo nodes per iteration. If there is a cycle, the fast pointer will eventually lap the slow pointer inside the loop. - Detect meeting: If
slow == fast, a cycle exists — returnTrue. - No cycle: If
fastreaches the end (None), the list is acyclic — returnFalse.
Time: O(n) | Space: O(1)
- Track visited nodes: Walk the list and add each node to a hash set.
- Detect repeat: If we encounter a node already in the set, we've found a cycle.
- End of list: If we reach
None, there is no cycle.
Time: O(n) | Space: O(n) — uses extra memory but is easier to reason about.
2. Linked List Cycle II (Leetcode:142)#
Problem Statement
Given the head of a linked list, return the node where the cycle begins. If there is no cycle, return null.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to (0-indexed). It is -1 if there is no cycle. Note that pos is not passed as a parameter.
Do not modify the linked list.
Example 1:
Input: head = [3,2,0,-4], pos = 1
Output: tail connects to node index 1
Explanation: There is a cycle in the linked list, where tail connects to the second node.
Example 2:
Input: head = [1,2], pos = 0
Output: tail connects to node index 0
Explanation: There is a cycle in the linked list, where tail connects to the first node.
Example 3:
Input: head = [1], pos = -1
Output: no cycle
Explanation: There is no cycle in the linked list.
Constraints:
The number of the nodes in the list is in the range [0, 10^4].
-10^5 <= Node.val <= 10^5
pos is -1 or a valid index in the linked-list.
Follow up:
Can you solve it using O(1) (i.e. constant) memory?
Code and Explanation
- Phase 1 — Find meeting point: Use Floyd's algorithm. When
slow == fast, both pointers are inside the cycle. - Phase 2 — Find cycle start: Reset
slowtohead. Move both pointers one step at a time. They meet at the node where the cycle begins. - Why it works: Let
a= distance from head to cycle start,b= distance from cycle start to meeting point, andc= cycle length. At meeting: slow traveleda + b, fast traveleda + b + nc. Since fast = 2 × slow:a + b = nc, soa = nc - b. Starting one pointer at head and one at the meeting point, both moving one step, they converge at the cycle start afterasteps.
Time: O(n) | Space: O(1)
- Track visited nodes: Walk the list, storing each node in a hash set.
- First repeat is the start: The first node we see twice is where the cycle begins.
- No cycle: Return
Noneif we reach the end.
Time: O(n) | Space: O(n)
3. Happy Number (Leetcode:202)#
Problem Statement
Write an algorithm to determine if a number n is happy.
A happy number is a number 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.
- Those numbers for which this process ends in 1 are happy.
Return true if n is a happy number, and false if not.
Example 1:
Input: n = 19
Output: true
Explanation:
1² + 9² = 82
8² + 2² = 68
6² + 8² = 100
1² + 0² + 0² = 1
Example 2:
Input: n = 2
Output: false
Constraints:
1 <= n <= 2^31 - 1
Code and Explanation
- Model as a linked list: Each number maps to the sum of squares of its digits — an implicit sequence that either reaches 1 or enters a cycle.
- Apply Floyd's algorithm: Treat
slowandfastas pointers in this sequence. If they meet at 1, the number is happy; if they meet at any other value, a non-1 cycle exists. - Helper
getNext: Computes the next number in the sequence by summing squared digits.
Time: O(log n) per step, bounded iterations | Space: O(1)
- Track seen values: Store each number in the sequence in a set.
- Cycle detection: If we revisit a number, we're in a loop that doesn't include 1.
- Success: If we reach 1, return
True.
Time: O(log n) | Space: O(log n)
4. Middle of the Linked List (Leetcode:876)#
Problem Statement
Given the head of a singly linked list, return the middle node of the linked list.
If there are two middle nodes, return the second middle node.
Example 1:
Input: head = [1,2,3,4,5]
Output: [3,4,5]
Explanation: The middle node of the list is node 3.
Example 2:
Input: head = [1,2,3,4,5,6]
Output: [4,5,6]
Explanation: Since the list has two middle nodes with values 3 and 4, we return the second one.
Constraints:
The number of nodes in the list is in the range
[1, 100].
1 <= Node.val <= 100
Code and Explanation
- Initialize: Both pointers start at
head. - Move at different speeds:
slowadvances one node,fastadvances two nodes per iteration. - Stop condition: When
fastcan no longer move (reaches end or last node),slowis at the middle. For even-length lists,slowlands on the second middle node becausefaststops when it reaches the last node, not past it.
Time: O(n) | Space: O(1)
- First pass: Count total nodes.
- Second pass: Walk
count // 2steps from head to reach the middle (second middle for even length). - Tradeoff: Requires two traversals but no pointer speed trick.
Time: O(n) | Space: O(1)
5. Palindrome Linked List (Leetcode:234)#
Problem Statement
Given the head of a singly linked list, return true if it is a palindrome or false otherwise.
Example 1:
Input: head = [1,2,2,1]
Output: true
Example 2:
Input: head = [1,2]
Output: false
Constraints:
The number of nodes in the list is in the range
[1, 105].
0 <= Node.val <= 9
Code and Explanation
- Find middle: Use fast/slow pointers.
slowends at the start of the second half. - Reverse second half: In-place reversal from
slowto end. - Compare halves: Walk
head(first half) andprev(reversed second half) in parallel, comparing values. - Why it works: A palindrome reads the same forward and backward; reversing the second half lets us compare both directions in O(n) time with O(1) extra space.
Time: O(n) | Space: O(1)
- Copy values: Store all node values in an array.
- Two-pointer check: Compare elements from both ends moving inward — classic palindrome test.
- Tradeoff: Simpler to implement but uses O(n) extra space.
Time: O(n) | Space: O(n)
6. Reorder List (Leetcode:143)#
Problem Statement
You are given the head of a singly linked-list. The list can be represented as:
L0 → L1 → ... → Ln - 1 → Ln
Reorder the list to be on the following form:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → ...
You may not modify the values in the list's nodes. Only nodes themselves may be changed.
Example 1:
Input: head = [1,2,3,4]
Output: [1,4,2,3]
Example 2:
Input: head = [1,2,3,4,5]
Output: [1,5,2,4,3]
Constraints:
The number of nodes in the list is in the range
[1, 5 * 10^4].
1 <= Node.val <= 1000
Code and Explanation
- Find middle: Fast/slow pointers. Stop
slowat the last node of the first half (fast.next.nextguard preventsslowfrom entering second half). - Split: Break the list at
slow.nextand setslow.next = None. - Reverse second half: Standard in-place reversal.
- Merge alternately: Interleave nodes from first half and reversed second half:
first → second → first.next → second.next → ...
Time: O(n) | Space: O(1)
- Find middle: Same fast/slow technique to split the list.
- Push second half onto stack: Stack gives LIFO access — nodes come out in reverse order.
- Merge alternately: Pop from stack and weave into the first half.
- Tradeoff: Uses O(n) stack space but avoids manual reversal logic.
Time: O(n) | Space: O(n)
7. Circular Array Loop (Leetcode:457)#
Problem Statement
You are playing a game involving a circular array of non-zero integers nums. Each nums[i] denotes the number of indices forward/backward you must move if you are located at index i:
- If
nums[i]is positive, movenums[i]steps forward, and - If
nums[i]is negative, movenums[i]steps backward.
Since the array is circular, you may assume that moving forward from the last element puts you on the first element, and moving backwards from the first element puts you on the last element.
A cycle in the array consists of a sequence of indices seq of length k where:
- Following the movement rules above results in the repeating index sequence
seq[0] -> seq[1] -> ... -> seq[k - 1] -> seq[0] -> ... - Every
nums[seq[j]]is either all positive or all negative. k > 1
Return true if there is a cycle in nums, or false otherwise.
Example 1:
Input: nums = [2,-1,1,2,2]
Output: true
Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward.
We can see the cycle 0 --> 2 --> 3 --> 0 --> ..., and all of its nodes are white (jumping in the same direction).
Example 2:
Input: nums = [-1,-2,-3,-4,-5,6]
Output: false
Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward.
The only cycle is of size 1, so we return false.
Example 3:
Input: nums = [1,-1,5,1,4]
Output: true
Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward.
We can see the cycle 0 --> 1 --> 0 --> ..., and while it is of size > 1, it has a node jumping forward and a node jumping backward, so it is not a cycle.
We can see the cycle 3 --> 4 --> 3 --> ..., and all of its nodes are white (jumping in the same direction).
Constraints:
1 <= nums.length <= 5000
-1000 <= nums[i] <= 1000
nums[i] != 0
Code and Explanation
- Treat indices as nodes: Each index
ipoints to(i + nums[i]) % n, forming an implicit linked list on a circular array. - Direction constraint: A valid cycle requires all nodes to move in the same direction (all positive or all negative). Break if direction flips.
- Floyd's detection: For each unvisited start index, run slow/fast. If they meet and cycle length > 1 (
slow != next_index(slow)), returnTrue. - Self-loop rejection: A cycle of size 1 (pointing to itself) is invalid per the problem.
Time: O(n²) worst case | Space: O(1)
- Try each start index: For each index, follow the jump sequence.
- Track visited indices: If we revisit an index within the same walk, a cycle of length > 1 exists (after direction and self-loop checks).
- Direction check: Abort if any jump reverses direction.
- Tradeoff: Uses O(n) space per attempt but is straightforward.
Time: O(n²) | Space: O(n)
8. Remove Nth Node From End of List (Leetcode:19)#
Problem Statement
Given the head of a linked list, remove the nth node from the end of the list and return its head.
Example 1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example 2:
Input: head = [1], n = 1
Output: []
Example 3:
Input: head = [1,2], n = 1
Output: [1]
Constraints:
The number of nodes in the list is sz.1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz`
**Follow up: ** Could you do this in one pass?
Code and Explanation
- Dummy node: Handles edge case where the head itself is removed.
- Create gap: Advance
fastn + 1steps ahead ofslowsoslowstops at the node before the one to delete. - Move together: When
fastreaches the end,slow.nextis the nth-from-end node — skip it. - One pass: Both pointers traverse the list exactly once.
Time: O(n) | Space: O(1)
- First pass: Count total nodes.
- Second pass: Walk to the
(length - n)-th node from the dummy and remove the next node. - Tradeoff: Two traversals but no gap-pointer trick needed.
Time: O(n) | Space: O(1)
9. Find the Duplicate Number (Leetcode:287)#
Problem Statement
Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive.
There is only one repeated number in nums, return this repeated number.
You must solve the problem without modifying the array nums and using only constant extra space.
Example 1:
Input: nums = [1,3,4,2,2]
Output: 2
Example 2:
Input: nums = [3,1,3,4,2]
Output: 3
Example 3:
Input: nums = [3,3,3,3,3]
Output: 3
Constraints:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
All the integers innumsappear only once except for precisely one integer which appears two or more times.
Follow up:
How can we prove that at least one duplicate number must exist in
nums?
Can you solve the problem in linear runtime complexity?
Code and Explanation
- Model as linked list: Treat index
ias a node pointing tonums[i]. Withn + 1values in range[1, n], the pigeonhole principle guarantees a duplicate, which creates a cycle. - Phase 1: Find meeting point inside the cycle using slow/fast pointers (move to
nums[slow]andnums[nums[fast]]). - Phase 2: Reset one pointer to
nums[0], move both one step at a time — they meet at the duplicate (cycle entrance). - Constraints satisfied: No array modification, O(1) extra space.
Time: O(n) | Space: O(1)
- Search space: The duplicate lies in
[1, n]. - Count ≤ mid: For each
mid, count how many array values are ≤mid. If count >mid, the duplicate is in the lower half (pigeonhole principle). - Narrow range: Binary search until
lo == hi— that's the duplicate. - Tradeoff: O(n log n) time but intuitive and doesn't rely on cycle math. Does not modify the array.
Time: O(n log n) | Space: O(1)