142. Linked List Cycle II
Problem Description
You are given the head of a linked list and need to find where a cycle begins in the list. If there is no cycle in the linked list, return null
.
A cycle exists in a linked list when a node can be reached again by continuously following the next
pointers. The problem internally uses pos
to indicate the index of the node that the tail's next
pointer connects to (0-indexed), where pos = -1
means there is no cycle. Note that pos
is not provided as a parameter to your function.
The task is to identify and return the exact node where the cycle starts. You must not modify the linked list structure while solving this problem.
For example, if you have a linked list like 3 -> 2 -> 0 -> -4
where the last node points back to the node with value 2
, then the cycle begins at the node with value 2
, and you should return a reference to that node.
The solution uses Floyd's Cycle Detection Algorithm (also known as the "tortoise and hare" algorithm) with two phases:
-
Cycle Detection Phase: Use two pointers - a slow pointer that moves one step at a time and a fast pointer that moves two steps at a time. If a cycle exists, these pointers will eventually meet at some node within the cycle.
-
Cycle Start Detection Phase: Once a cycle is detected, reset one pointer to the head of the list while keeping the other at the meeting point. Move both pointers one step at a time. The node where they meet again is the start of the cycle.
The mathematical proof behind this works because if the distance from head to cycle start is x
, the distance from cycle start to where pointers first meet is y
, and the remaining distance in the cycle is z
, then the relationship x = (k-1) × (y+z) + z
holds true (where k
is the number of complete cycles the fast pointer traveled). This means moving x
steps from both the head and the meeting point will converge at the cycle's starting node.
Intuition
The key insight comes from thinking about what happens when two runners move through a circular track at different speeds. If one runner moves twice as fast as the other, they will eventually meet again somewhere on the track - this forms the basis of cycle detection.
When we have a linked list with a cycle, we can imagine it as a path that leads to a circular loop. Using two pointers moving at different speeds (slow moves 1 step, fast moves 2 steps), if there's a cycle, the fast pointer will eventually "lap" the slow pointer and they'll meet. If there's no cycle, the fast pointer will simply reach the end of the list.
But finding that a cycle exists isn't enough - we need to find where it starts. This is where the mathematical relationship becomes clever. When the pointers meet inside the cycle, they've traveled different distances. The slow pointer traveled distance x + y
(where x
is the distance to the cycle start, and y
is some distance into the cycle). The fast pointer traveled 2(x + y)
because it moves twice as fast.
Since the fast pointer is also somewhere in the cycle when they meet, we can express its distance as x + y + k(y + z)
, where k
is the number of complete loops it made, and y + z
is the cycle length.
Setting these equal: 2(x + y) = x + y + k(y + z)
Simplifying: x + y = k(y + z)
Therefore: x = k(y + z) - y = (k-1)(y + z) + z
This reveals something remarkable: the distance from the head to the cycle start (x
) equals the distance from the meeting point to the cycle start (z
) plus some number of complete loops. This means if we place one pointer at the head and keep another at the meeting point, then move both one step at a time, they will meet exactly at the cycle's starting node - because they'll both travel the same distance x
to get there.
Solution Approach
The implementation uses Floyd's Cycle Detection Algorithm with two distinct phases:
Phase 1: Detect if a cycle exists
We initialize two pointers, fast
and slow
, both starting at the head
of the linked list. The algorithm moves these pointers through the list:
slow
pointer moves one step at a time:slow = slow.next
fast
pointer moves two steps at a time:fast = fast.next.next
We continue this movement while fast
and fast.next
are not null (ensuring we don't get null pointer errors). If the pointers meet (slow == fast
), we've confirmed a cycle exists. If fast
reaches the end of the list, there's no cycle and we return null
.
Phase 2: Find the cycle's starting node
Once we've confirmed a cycle exists and have the meeting point, we create a new pointer ans
starting at the head
of the linked list. We then move both ans
and slow
pointers one step at a time:
ans = ans.next
slow = slow.next
These pointers will meet at the cycle's entrance node. This works because of the mathematical relationship we established: the distance from the head to the cycle start equals the distance from the meeting point to the cycle start (plus potentially some complete loops of the cycle).
Why the algorithm works:
Let's trace through the distances:
- Distance from head to cycle entrance:
x
- Distance from cycle entrance to meeting point:
y
- Distance from meeting point back to cycle entrance:
z
- Cycle length:
y + z
When the pointers meet:
- Slow pointer traveled:
x + y
- Fast pointer traveled:
x + y + k(y + z)
wherek
is the number of complete cycles
Since fast moves twice as fast: 2(x + y) = x + y + k(y + z)
Simplifying: x = (k - 1)(y + z) + z
This equation tells us that moving x
steps from both the head and the meeting point will converge at the cycle start. The pointer from the meeting point will travel z
steps plus potentially (k-1)
complete loops before meeting the pointer from the head.
The algorithm has O(n)
time complexity where n
is the number of nodes, and O(1)
space complexity since we only use a constant number of pointers.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with the linked list: 1 -> 2 -> 3 -> 4 -> 5 -> 3
(the last node points back to node 3, creating a cycle).
Initial structure: 1 -> 2 -> 3 -> 4 -> 5 ^ | |_________|
Phase 1: Cycle Detection
We start with both slow
and fast
pointers at node 1 (head).
Step 1:
slow
moves to node 2fast
moves to node 3
Step 2:
slow
moves to node 3fast
moves from node 3 -> 4 -> 5
Step 3:
slow
moves to node 4fast
moves from node 5 -> 3 -> 4
Step 4:
slow
moves to node 5fast
moves from node 4 -> 5 -> 3
Step 5:
slow
moves to node 3fast
moves from node 3 -> 4 -> 5
Step 6:
slow
moves to node 4fast
moves from node 5 -> 3 -> 4
The pointers meet at node 4! This confirms a cycle exists.
Phase 2: Finding Cycle Start
Now we place ans
pointer at head (node 1) and keep slow
at the meeting point (node 4).
Move both pointers one step at a time:
Step 1:
ans
moves from node 1 to node 2slow
moves from node 4 to node 5
Step 2:
ans
moves from node 2 to node 3slow
moves from node 5 to node 3
Both pointers meet at node 3 - this is where the cycle begins!
Verification of the math:
- Distance from head to cycle start (
x
) = 2 steps (1 -> 2 -> 3) - Distance from cycle start to meeting point (
y
) = 2 steps (3 -> 4) - Distance from meeting point back to cycle start (
z
) = 2 steps (4 -> 5 -> 3) - Cycle length = 3 nodes (3 -> 4 -> 5 -> 3)
When they first met:
- Slow traveled: x + y = 2 + 2 = 4 steps
- Fast traveled: 2(x + y) = 8 steps = x + y + k(y + z) = 2 + 2 + 1×3 + 1 (one complete cycle plus one extra step)
The equation x = z holds true here (both equal 2), so moving 2 steps from both the head and meeting point converges at node 3, the cycle's start.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, x):
4# self.val = x
5# self.next = None
6
7from typing import Optional
8
9class Solution:
10 def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
11 """
12 Detects the node where a cycle begins in a linked list.
13 Uses Floyd's Cycle Detection Algorithm (Tortoise and Hare).
14
15 Args:
16 head: The head node of the linked list
17
18 Returns:
19 The node where the cycle begins, or None if no cycle exists
20 """
21 # Initialize two pointers - fast (hare) and slow (tortoise)
22 fast_pointer = slow_pointer = head
23
24 # Phase 1: Detect if a cycle exists using Floyd's algorithm
25 # Fast pointer moves 2 steps, slow pointer moves 1 step
26 while fast_pointer and fast_pointer.next:
27 slow_pointer = slow_pointer.next
28 fast_pointer = fast_pointer.next.next
29
30 # If pointers meet, a cycle exists
31 if slow_pointer == fast_pointer:
32 # Phase 2: Find the start of the cycle
33 # Move one pointer to head and keep the other at meeting point
34 start_pointer = head
35
36 # Move both pointers one step at a time until they meet
37 # The meeting point is the start of the cycle
38 while start_pointer != slow_pointer:
39 start_pointer = start_pointer.next
40 slow_pointer = slow_pointer.next
41
42 return start_pointer
43
44 # No cycle found
45 return None
46
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode(int x) {
7 * val = x;
8 * next = null;
9 * }
10 * }
11 */
12public class Solution {
13 /**
14 * Detects if a linked list has a cycle and returns the node where the cycle begins.
15 * Uses Floyd's Cycle-Finding Algorithm (Tortoise and Hare).
16 *
17 * @param head The head of the linked list
18 * @return The node where the cycle begins, or null if no cycle exists
19 */
20 public ListNode detectCycle(ListNode head) {
21 // Initialize two pointers: fast (hare) and slow (tortoise)
22 ListNode fastPointer = head;
23 ListNode slowPointer = head;
24
25 // Phase 1: Detect if a cycle exists using two-pointer technique
26 // Fast pointer moves 2 steps, slow pointer moves 1 step
27 while (fastPointer != null && fastPointer.next != null) {
28 slowPointer = slowPointer.next; // Move slow pointer one step
29 fastPointer = fastPointer.next.next; // Move fast pointer two steps
30
31 // If pointers meet, a cycle exists
32 if (slowPointer == fastPointer) {
33 // Phase 2: Find the start of the cycle
34 // Move one pointer to head and keep the other at meeting point
35 ListNode startPointer = head;
36
37 // Move both pointers one step at a time until they meet
38 // The meeting point is the start of the cycle
39 while (startPointer != slowPointer) {
40 startPointer = startPointer.next;
41 slowPointer = slowPointer.next;
42 }
43
44 // Return the node where the cycle begins
45 return startPointer;
46 }
47 }
48
49 // No cycle found
50 return null;
51 }
52}
53
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode(int x) : val(x), next(NULL) {}
7 * };
8 */
9class Solution {
10public:
11 /**
12 * Detects if a linked list has a cycle and returns the node where the cycle begins.
13 * Uses Floyd's Cycle Detection Algorithm (Tortoise and Hare).
14 *
15 * @param head The head of the linked list
16 * @return The node where the cycle begins, or nullptr if no cycle exists
17 */
18 ListNode* detectCycle(ListNode* head) {
19 // Initialize two pointers for cycle detection
20 ListNode* fastPointer = head;
21 ListNode* slowPointer = head;
22
23 // Phase 1: Detect if a cycle exists using two pointers moving at different speeds
24 while (fastPointer != nullptr && fastPointer->next != nullptr) {
25 // Move slow pointer one step forward
26 slowPointer = slowPointer->next;
27 // Move fast pointer two steps forward
28 fastPointer = fastPointer->next->next;
29
30 // If pointers meet, a cycle exists
31 if (slowPointer == fastPointer) {
32 // Phase 2: Find the start of the cycle
33 // Mathematical proof: Distance from head to cycle start equals
34 // distance from meeting point to cycle start
35 ListNode* startPointer = head;
36
37 // Move both pointers one step at a time until they meet
38 while (startPointer != slowPointer) {
39 startPointer = startPointer->next;
40 slowPointer = slowPointer->next;
41 }
42
43 // The meeting point is the start of the cycle
44 return startPointer;
45 }
46 }
47
48 // No cycle detected
49 return nullptr;
50 }
51};
52
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Detects if a linked list has a cycle and returns the node where the cycle begins.
15 * Uses Floyd's Cycle Detection Algorithm (Tortoise and Hare).
16 *
17 * @param head - The head of the linked list
18 * @returns The node where the cycle begins, or null if no cycle exists
19 */
20function detectCycle(head: ListNode | null): ListNode | null {
21 // Initialize two pointers: slow moves one step, fast moves two steps
22 let slowPointer: ListNode | null = head;
23 let fastPointer: ListNode | null = head;
24
25 // Phase 1: Detect if a cycle exists using Floyd's algorithm
26 while (fastPointer !== null && fastPointer.next !== null) {
27 // Move slow pointer one step forward
28 slowPointer = slowPointer!.next;
29 // Move fast pointer two steps forward
30 fastPointer = fastPointer.next.next;
31
32 // If pointers meet, a cycle exists
33 if (slowPointer === fastPointer) {
34 // Phase 2: Find the start of the cycle
35 // Reset one pointer to head and keep the other at meeting point
36 let startPointer: ListNode | null = head;
37
38 // Move both pointers one step at a time until they meet
39 // The meeting point is the start of the cycle
40 while (startPointer !== slowPointer) {
41 startPointer = startPointer!.next;
42 slowPointer = slowPointer!.next;
43 }
44
45 // Return the node where the cycle begins
46 return startPointer;
47 }
48 }
49
50 // No cycle detected
51 return null;
52}
53
Time and Space Complexity
Time Complexity: O(n)
, where n
is the number of nodes in the linked list.
The algorithm uses Floyd's Cycle Detection (Tortoise and Hare) approach. In the worst case:
- The first while loop runs until the slow and fast pointers meet inside the cycle. The slow pointer travels at most
n
nodes before meeting the fast pointer. - The second while loop runs to find the start of the cycle. This loop runs at most
n
iterations as it traverses from the head to the cycle's starting point. - Therefore, the total time complexity is
O(n) + O(n) = O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Three pointer variables:
fast
,slow
, andans
- No additional data structures are created regardless of the input size
- The space usage remains constant independent of the number of nodes in the linked list.
Common Pitfalls
1. Incorrect Cycle Detection Check
A common mistake is checking if the pointers meet before moving them, which causes the algorithm to miss cycles when the head itself is part of the cycle.
Incorrect Implementation:
while fast_pointer and fast_pointer.next:
# Wrong: checking before moving
if slow_pointer == fast_pointer:
# This will never trigger on first iteration
break
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next
Solution: Always move the pointers first, then check for equality. This ensures that even if both pointers start at the same position (head), they separate and can properly detect the cycle.
2. Not Handling Edge Cases Properly
Failing to check for null values before accessing fast_pointer.next.next
can cause runtime errors.
Incorrect Implementation:
# Dangerous: not checking fast_pointer.next
while fast_pointer:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next # Can throw error if fast_pointer.next is None
Solution: Always check both fast_pointer
and fast_pointer.next
before advancing the fast pointer two steps.
3. Confusing the Meeting Point with Cycle Start
A critical misunderstanding is thinking that where the pointers first meet is the cycle's starting point. The meeting point is typically somewhere within the cycle, not at its entrance.
Incorrect Implementation:
while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next
if slow_pointer == fast_pointer:
return slow_pointer # Wrong: this is the meeting point, not cycle start
Solution: After detecting a cycle, implement Phase 2 where you reset one pointer to head and move both pointers one step at a time until they meet at the cycle's entrance.
4. Moving Pointers at Wrong Speed in Phase 2
In the second phase, both pointers must move at the same speed (one step at a time). Moving them at different speeds will not find the cycle start.
Incorrect Implementation:
# After detecting cycle
start_pointer = head
while start_pointer != slow_pointer:
start_pointer = start_pointer.next
slow_pointer = slow_pointer.next.next # Wrong: should move one step only
Solution: In Phase 2, both pointers must advance exactly one step per iteration to ensure they meet at the cycle's starting node.
5. Single Node Self-Loop Edge Case
When a single node points to itself, both pointers might not separate properly if the initial check is done incorrectly.
Robust Implementation:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return None
fast_pointer = slow_pointer = head
# Phase 1: Detect cycle
while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next
if slow_pointer == fast_pointer:
# Phase 2: Find cycle start
start_pointer = head
while start_pointer != slow_pointer:
start_pointer = start_pointer.next
slow_pointer = slow_pointer.next
return start_pointer
return None
This implementation correctly handles all edge cases including empty lists, single nodes, and self-loops.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!