Opening the reading…
Opening the reading…
LINKED LIST › LINKED LIST (PART 1)
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →A singly linked list without a cycle eventually reaches null. With a cycle, every pointer that enters the cycle keeps moving around the same ring. Let the path from head to the cycle start have length a, let the cycle length be c, and let the meeting point be b steps around the cycle from its start. The slow pointer has travelled a + b steps when the two pointers meet.
The fast pointer has travelled twice as far, so the extra distance is also a whole number of laps around the cycle. That makes the meeting point special: walking forward from it until the cycle start takes the same number of steps, modulo the cycle length, as walking from head to the cycle start. Reset one pointer to head and move both one step at a time; their first meeting is therefore the entrance.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | The detection phase advances through the non-cyclic prefix and at most a constant number of laps before the pointers meet; the reset phase advances at most the prefix length plus one cycle length. Both phases therefore visit O(n) pointer positions, without storing or revisiting an unbounded history. |
| Space | O(1) | Only the two pointer variables are used, and the returned node is output rather than working memory. The bound stays O(1) for an empty list, a long prefix, a one-node cycle, and every other input shape. |
#include <vector>
using namespace std;
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return nullptr;
}
};The key placement is the reset inside the meeting branch. The first equality only establishes that both pointers are on the cycle. Moving slow back to head and then advancing both equally changes the comparison from 'where did the speeds coincide?' to 'where do the prefix distance and the remaining cycle distance coincide?' That is why the returned pointer is the entrance rather than an arbitrary cycle node.
A hash set gives a direct interpretation: walk one node at a time, and return the first node whose address has already appeared. This is not an optimisation; it trades O(1) extra space for a simpler invariant. It is reasonable when clarity matters more than memory, while Floyd's method is the required choice when constant extra space is important.
#include <unordered_set>
using namespace std;
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
unordered_set<ListNode*> seen;
ListNode* current = head;
while (current != nullptr) {
if (seen.count(current) != 0) {
return current;
}
seen.insert(current);
current = current->next;
}
return nullptr;
}
};