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 gives you only one direction to follow. If the next pointer eventually becomes null, a walk ends. If the list contains a cycle, the walk never ends because every node in the cycle points to another node in that same loop. The challenge is detecting repetition without storing every node you have visited.
Make one pointer move one node at a time and another move two nodes at a time. In an acyclic list, the fast pointer reaches null. In a cyclic list, both pointers eventually remain inside the loop, and the fast pointer gains one node on the slow pointer during every iteration. Moving around a finite loop while gaining one position per step guarantees a meeting.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | In an acyclic list, fast reaches null after passing through the nodes. In a cyclic list, the pointers first spend at most the prefix length entering the cycle, then meet after at most one cycle-length worth of relative movement; the prefix and cycle together contain no more than n nodes. |
| Space | O(1) extra | Only slow and fast are stored, so the working memory stays constant regardless of whether the list is straight or cycles immediately. The algorithm does not allocate output storage; it returns a boolean. |
#include <cstddef>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
bool hasCycle(ListNode* head) {
if (!head || !head->next) return false;
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (!fast || !fast->next) return false;
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};The important placement is the guard immediately before fast->next->next. At the top of each loop, fast may already be null, or fast->next may be null. Checking both before dereferencing keeps the traversal safe and uses the same fact to conclude that no cycle exists. Once the loop stops naturally, slow == fast is not an ordinary crossing; linked-list pointers can only become equal by landing on the same node.
You can detect the first repeated node directly with a hash set. Before following a node, check whether its address has already been recorded; a repeat means a cycle, while reaching null means the list ends. This is a different arrangement rather than an optimisation: it is often easier to explain, but it spends O(n) extra space instead of O(1).
#include <cstddef>
#include <unordered_set>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
bool hasCycle(ListNode* head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head) != 0) return true;
seen.insert(head);
head = head->next;
}
return false;
}
};