The Tortoise and The Hare
[!NOTE] This module explores the core principles of Fast & Slow Pointers, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. Problem Definition
Problem Statement: Given the head of a linked list, determine if it 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.
Constraints:
- The number of the nodes in the list is in the range
[0, 10<sup>4</sup>]. -10<sup>5</sup> ≤ Node.val ≤ 10<sup>5</sup>.
Examples:
- Input:
head = [3,2,0,-4],pos = 1(tail connects to node index 1) Output:true - Input:
head = [1,2],pos = 0(tail connects to node index 0) Output:true - Input:
head = [1],pos = -1(no cycle) Output:false
Edge Cases & Clarifying Questions:
- Q: Can the list be empty? A: Yes, an empty list has no cycle.
-
Q: Can the list have only one node? A: Yes, if its
nextpointer is null, there is no cycle. If it points to itself, there is a cycle.
2. Interactive Analysis
Watch the race. Can the Hare catch the Tortoise?
3. Intuition (The “Genesis”)
Brute Force Approach:
The simplest way to detect a cycle is to keep track of every node we visit. We can iterate through the list and store each node in a HashSet. If we encounter a node that is already in our set, a cycle exists.
- Time Complexity: O(N) because we visit each node at most once.
- Space Complexity: O(N) because we need to store up to N nodes in the
HashSet.
Optimized Approach (Floyd’s Cycle-Finding Algorithm): Also known as the Tortoise and Hare pattern. We can optimize the space complexity to O(1) using two pointers moving at different speeds.
- Slow Pointer (Tortoise): Moves 1 step at a time.
- Fast Pointer (Hare): Moves 2 steps at a time. If there is a cycle, the fast pointer will eventually “lap” the slow pointer and they will meet at the same node.
The Convergence Proof
- Let m be the distance from the
headto the start of the cycle. - Let k be the length of the cycle.
- Let i be the number of steps taken.
- Position of Tortoise: S(i) = (i - m) mod k (once inside the cycle).
- Position of Hare: F(i) = (2i - m) mod k.
- The pointers meet when F(i) ≡ S(i) mod k.
- This implies (2i - m) - (i - m) = n · k (for some integer n).
- Simplifying gives i = n · k. Since i can be any multiple of the cycle length k, and the Tortoise hasn’t even finished one lap when the Hare (moving twice as fast) laps it, they are guaranteed to meet.
Common Pitfalls:
- Null Pointer Exceptions: Forgetting to check if
fastorfast.nextis null before advancing the fast pointer by two steps. -
Returning the Meeting Node: This algorithm only detects a cycle. The node where they meet is not necessarily the start of the cycle.
4. Implementation
We initialize both pointers at head.
- If
fastreachesnull, the list ends (no cycle). - If
fast == slow, there is a cycle.
Java
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; // 1 step
fast = fast.next.next; // 2 steps
if (slow == fast) {
return true; // Cycle detected
}
}
return false; // Reached null
}
Go
func HasCycle(head *ListNode) bool {
if head == nil {
return false
}
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
}
5. Complexity Analysis
| Strategy | Time | Space | Notes |
|---|---|---|---|
| Floyd’s Algorithm | O(N) | O(1) | Guaranteed to meet within one cycle length. |
| Hash Set | O(N) | O(N) | Naive approach. |