Linked List Cycle II
MedPrompt
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, 104]. -105 <= Node.val <= 105posis-1or a valid index in the linked-list.
Follow up: Can you solve it using O(1) (i.e. constant) memory?
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through the linked list and storing each visited node in a hash set. If we encounter a node that is already in the set, we have found the starting point of the cycle.
Algorithm
- Create an empty
HashSet<ListNode>calledvisited. - Initialize a pointer
current = head. - Loop while
currentis notnull:- If
visitedcontainscurrent, then a cycle is detected. Returncurrentas it's the start of the cycle. - Add
currentto thevisitedset. - Move to the next node:
current = current.next.
- If
- If the loop completes, it means no cycle was found. Return
null.
Walkthrough
We can solve this problem by using a hash set to keep track of the nodes we have already visited.
- Initialize an empty
HashSetto storeListNodeobjects. - Start traversing the list from the
headnode with a pointer, let's call itcurrent. - In each step of the traversal, check if the
currentnode is already present in the hash set. - If the hash set already contains the
currentnode, it means we have visited this node before, and it is the first node of the cycle. We return thiscurrentnode. - If the
currentnode is not in the hash set, we add it to the set and move to the next node (current = current.next). - If we reach the end of the list (
currentbecomesnull), it signifies that there is no cycle. In this case, we returnnull.
/** * 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) { if (head == null) { return null; } Set<ListNode> visited = new HashSet<>(); ListNode current = head; while (current != null) { if (visited.contains(current)) { return current; } visited.add(current); current = current.next; } return null; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Simple and intuitive to understand and implement.
Guaranteed to find the cycle start if one exists.
Cons
Requires extra space to store visited nodes, which can be up to the size of the list.
Does not meet the O(1) space complexity follow-up requirement.
This is an optimal, two-pointer approach that solves the problem in constant space. It consists of two phases: first, detecting if a cycle exists, and second, finding the exact node where the cycle begins.
Algorithm
- Initialize two pointers,
slowandfast, both tohead. - Phase 1: Detect Cycle
- Loop as long as
fastandfast.nextare notnull.- Move
slowone step:slow = slow.next. - Move
fasttwo steps:fast = fast.next.next. - If
slowandfastmeet (slow == fast), a cycle is detected. Break the loop and proceed to Phase 2.
- Move
- Loop as long as
- If the loop finishes without the pointers meeting, there is no cycle. Return
null. - Phase 2: Find Cycle Start
- Reset one pointer to the beginning of the list:
ptr1 = head. - Keep the other pointer at the meeting point:
ptr2 = slow. - Move both
ptr1andptr2one step at a time until they meet (ptr1 == ptr2). - The node where they meet is the start of the cycle. Return this node.
- Reset one pointer to the beginning of the list:
Walkthrough
This classic algorithm, also known as the two-pointer algorithm, elegantly solves the problem without using extra space.
Phase 1: Cycle Detection
We use two pointers, slow and fast. The slow pointer moves one step at a time, while the fast pointer moves two steps at a time.
- If the list has no cycle, the
fastpointer (orfast.next) will eventually becomenull. - If there is a cycle, the
fastpointer will eventually enter the cycle and lap theslowpointer. They are guaranteed to meet at some node within the cycle.
Phase 2: Finding the Cycle's Start Node
Once slow and fast meet, we can find the start of the cycle. Let's analyze the distances:
- Let
Lbe the distance from theheadto the cycle's start node. - Let
Cbe the length of the cycle. - Let
kbe the distance from the cycle's start node to the meeting point.
When they meet:
- Distance traveled by
slow=L + k - Distance traveled by
fast=L + k + n*C(for some integern >= 1)
Since fast moves twice as fast as slow:
2 * (L + k) = L + k + n*C
2L + 2k = L + k + n*C
L + k = n*C
L = n*C - k
This equation tells us that the distance from the head to the cycle start (L) is equal to n full cycles minus k. This implies that if we start one pointer from the head and another from the meeting point, and move them one step at a time, they will meet at the cycle's start node.
- A pointer starting at
headwill travelLsteps to reach the start. - A pointer starting at the meeting point (which is
ksteps into the cycle) needs to travelC - ksteps to reach the start. The equationL = n*C - kcan be rewritten asL = (n-1)C + (C-k). This confirms that afterLsteps, the pointer from the meeting point will also be at the cycle start.
So, after the meeting, we reset one pointer to the head and keep the other at the meeting point. Then we advance both one step at a time until they meet again. This new meeting point is the start of the cycle.
/** * 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) { if (head == null || head.next == null) { return null; } ListNode slow = head; ListNode fast = head; boolean hasCycle = false; // Phase 1: Detect if a cycle exists while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { hasCycle = true; break; } } if (!hasCycle) { return null; } // Phase 2: Find the start of the cycle ListNode ptr1 = head; ListNode ptr2 = slow; // or fast, as they are at the same meeting point while (ptr1 != ptr2) { ptr1 = ptr1.next; ptr2 = ptr2.next; } return ptr1; }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Extremely efficient in terms of memory, using only O(1) extra space.
Time complexity is linear, O(N).
Does not modify the linked list.
Cons
The logic, particularly for finding the start node, is more complex and less intuitive than the hash set method.
Solutions
Solution
/** * 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 fast = head , slow = head ; while ( fast != null && fast . next != null ) { slow = slow . next ; fast = fast . next . next ; if ( slow == fast ) { ListNode ans = head ; while ( ans != slow ) { ans = ans . next ; slow = slow . next ; } return ans ; } } return null ; } }/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} head * @return {ListNode} */ var detectCycle = function ( head ) { let [ slow , fast ] = [ head , head ]; while ( fast && fast . next ) { slow = slow . next ; fast = fast . next . next ; if ( slow === fast ) { let ans = head ; while ( ans !== slow ) { ans = ans . next ; slow = slow . next ; } return ans ; } } return null ; };/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode * detectCycle ( ListNode * head ) { ListNode * fast = head ; ListNode * slow = head ; while ( fast && fast -> next ) { slow = slow -> next ; fast = fast -> next -> next ; if ( slow == fast ) { ListNode * ans = head ; while ( ans != slow ) { ans = ans -> next ; slow = slow -> next ; } return ans ; } } return nullptr ; } };# 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 has_cycle = False while not has_cycle and fast and fast . next : slow , fast = slow . next , fast . next . next has_cycle = slow == fast if not has_cycle : return None p = head while p != slow : p , slow = p . next , slow . next return p ############# class Solution : def detectCycle ( self , head : Optional [ ListNode ]) -> Optional [ ListNode ]: fast = slow = head while fast and fast . next : slow = slow . next fast = fast . next . next if slow == fast : ans = head while ans != slow : ans = ans . next slow = slow . next return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.