Linked List Cycle - Solution
| LeetCode #141 | Difficulty: Easy |
Approach
Problem
Detect if a linked list has a cycle. A cycle exists if a node’s next pointer eventually points back to a previously visited node.
Two Strategies
1. Brute Force - Hash Map (Cache)
- Store each visited node in a hash map as we traverse
- If we encounter a node already in the map, a cycle exists
- Time: O(n), Space: O(n)
2. Optimized - Two Pointers (Floyd’s Cycle Detection)
- Use slow pointer (moves 1 step) and fast pointer (moves 2 steps)
- If they meet, a cycle exists; if fast reaches null, no cycle
- Time: O(n), Space: O(1)
The two-pointer approach is optimal because it uses constant space and detects cycles in a single pass.
Algorithm Explanation
Brute Force Approach
- Create an empty hash map to track visited nodes
- Traverse the list with a position counter
- For each node:
- Check if it exists in the hash map
- If yes, return
true(cycle detected) - If no, add it to the map with current position
- Move to the next node
- If we reach
null, returnfalse(no cycle)
Optimized Approach (Floyd’s Algorithm)
- Initialize:
slowpointer tonullptr(will advance 1 step)fastpointer tohead(will advance 2 steps)
- Traverse while
fastandfast->nextare not null:- Check if
slow == fast→ cycle detected - Move
slowforward by 1 (or start at head if null) - Move
fastforward by 2
- Check if
- If fast reaches null, no cycle exists
Why it works: In a cycle, the fast pointer “laps” the slow pointer, and they must meet at some point.
Complexity Analysis
Brute Force
- Time Complexity: O(n) - traverse each node once
- Space Complexity: O(n) - hash map stores all visited nodes
Optimized (Two Pointers)
- Time Complexity: O(n) - traverse the list once; if cycle exists, they meet within n steps
- Space Complexity: O(1) - only two pointers, no extra data structures
C++ Solution
Brute Force Approach
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_map<ListNode *, int> cache;
ListNode* cur = head;
int curPos = 0;
while(cur != nullptr) {
if(cache.count(cur) != 0) {
int pos = cache[cur];
cout<<pos<<endl;
return true;
} else {
cache[cur] = curPos++;
}
cur = cur->next;
}
return false;
}
};
Optimized Approach (Floyd’s Cycle Detection)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = nullptr;
ListNode* fast = head;
while(fast != nullptr && fast->next != nullptr) {
if(slow == fast) return true;
slow = (slow == nullptr) ? head : slow->next;
fast = fast->next->next;
}
return false;
}
};
Key Insights
- Floyd’s Algorithm: The two-pointer technique is elegant and space-efficient
- Pointer Comparison: We compare pointers (memory addresses), not values
- Meeting Point: In a cycle, fast pointer catches slow pointer guaranteed
- No Extra Space: Unlike hash map, pointers use constant space
- Single Pass: Both approaches traverse the list once effectively