The "Linked List Cycle II" problem asks you to detect if a given singly linked list contains a cycle. If a cycle exists, you must return the node where the cycle begins. If there is no cycle, return null
.
head
of a singly linked list.
For example, given a list: 3 → 2 → 0 → -4
where -4
points back to 2
, you should return the node with value 2
.
At first glance, the problem seems to require traversing the list and checking if any node is visited more than once. One way is to use a data structure (like a hash set) to record visited nodes. If we encounter the same node again, we have found the start of the cycle.
However, the problem asks for an optimized solution with constant space. This leads us to consider the classic "Floyd’s Tortoise and Hare" algorithm, which uses two pointers moving at different speeds to detect cycles without extra space.
The challenge is not only to detect the cycle but also to find the node where the cycle begins. This requires a deeper understanding of how pointers interact as they traverse the list.
We use Floyd's Tortoise and Hare algorithm, which consists of two main phases:
slow
and fast
, both at head
.slow
one step at a time, and fast
two steps at a time.fast
or fast.next
becomes null
, there is no cycle.slow
and fast
meet, a cycle exists.slow
to head
, keep fast
at the meeting point.
Why does this work? The distance from head
to the cycle start equals the distance from the meeting point to the cycle start (when moving at the same speed).
This approach uses only two pointers and no extra memory, achieving O(1) space and O(n) time.
Suppose we have a linked list: 3 → 2 → 0 → -4
, with -4
pointing back to 2
.
slow
at 3, fast
at 3slow
at 2, fast
at 0slow
at 0, fast
at 2slow
at -4, fast
at -4 (they meet!)slow
to head (3), fast
stays at -4slow
at 2, fast
at 2 (they meet!)2
.
The optimized approach is preferred for its constant space usage and linear time.
To solve the Linked List Cycle II problem, we use Floyd's Tortoise and Hare algorithm. This elegant approach detects cycles using two pointers and then cleverly locates the cycle's start by exploiting pointer movement properties. The solution is optimal, requiring only constant space and linear time, and highlights the power of pointer manipulation in linked list problems.
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = fast = head
# Phase 1: Detect if a cycle exists
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return None
# Phase 2: Find the entry point
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
// Definition for singly-linked list.
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
// Phase 1: Detect if a cycle exists
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
break;
}
if (!fast || !fast->next)
return nullptr;
// Phase 2: Find the entry point
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
// Definition for singly-linked list.
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
// Phase 1: Detect if a cycle exists
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast)
break;
}
if (fast == null || fast.next == null)
return null;
// Phase 2: Find the entry point
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
var detectCycle = function(head) {
let slow = head;
let fast = head;
// Phase 1: Detect if a cycle exists
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break;
}
if (!fast || !fast.next) return null;
// Phase 2: Find the entry point
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
};