Pointer Techniques
What is this?
Two runners set off around a track, one at twice the speed of the other. If the track is a straight line, the fast runner finishes and the question is over. If it loops, the fast runner must eventually lap the slow one — they meet, and the meeting itself proves the loop exists. No map of the track, no marking the ground, no memory of where you have been.
That is the first half of this chapter. The second half is a different kind of cleverness: when you need to remember a mapping from every old node to its copy, and are not allowed a hash map, you can build the mapping into the list itself.
→ they meet at the loop entry"] C --> C1["weave each copy behind its source"] C1 --> C2["copy.random = node.random.next"] C2 --> C3["unweave into two clean lists"]
💡 Fun fact: why does resetting one pointer to the head find the loop's entrance? Let
abe the distance from head to entry andbthe distance from entry to the meeting point. When they meet, the slow pointer has walkeda + band the fast one exactly twice that — so the extra distance the fast pointer covered is a whole number of loops. That forcesato equal the remaining distance from the meeting point back around to the entry. Two pointers stepping one at a time from the head and from the meeting point therefore arrive together, at the entry.
🔓 The 2 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: with one successor per node and no spare memory, you buy global knowledge either by running two pointers at different rates or by temporarily rewriting the structure so the information you need becomes locally reachable — then restoring it.
1. Two speeds
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True # they can only meet inside a loop
return False # fast fell off the end
The loop condition is the part to get right: fast and fast.next, because the fast pointer takes two steps and either could walk off the end. Checking only fast crashes on even-length lists.
The same skeleton, retuned, gives you the middle node (when fast reaches the end, slow is halfway) and the k-th from the end (start fast k nodes ahead, then advance both until fast hits the end). One pass each, no length precomputation.
2. Interleave instead of mapping
To deep-copy a list whose nodes carry an arbitrary random pointer, the obvious solution maps each original node to its copy — O(n) space. The trick removes the map by making each copy findable from its source:
pass 1 — weave: A → A' → B → B' → C → C'
pass 2 — randoms: A'.random = A.random.next ← the copy of whatever A pointed at
pass 3 — unweave: A → B → C and A' → B' → C'
Pass 2 is the whole idea: because every copy sits immediately behind its original, "the copy of node X" is always X.next. The map is not gone — it is encoded in the list's own shape.
Pass 3 must restore the original list exactly. Leaving it interleaved is a correctness failure, not a tidiness one: the caller still holds a reference to it.
3. A 30-second worked example (finding the loop entry)
1 → 2 → 3 → 4 → 5
↑ |
└───────┘ entry is node 3, loop length 3
step slow fast
1 2 3
2 3 5
3 4 4 ← meet at node 4
reset one pointer to head, advance both by one:
1 4
2 5
3 3 ← meet at node 3, the entry ✓
Distance head→entry is 2, and distance meeting-point→entry going forward around the loop is also 2. That equality is exactly what the fun-fact argument above proves.
4. Where you'll actually meet this
- Dependency and build systems. Detecting import cycles or circular module dependencies, where the graph is walked rather than stored.
- Pseudo-random generators. Finding a generator's period is cycle detection on the sequence it produces — Floyd's original motivation, and still how PRNG quality is measured.
- Pollard's rho factorisation. The same tortoise-and-hare, applied to a numeric sequence, is a practical integer-factorisation method.
- Filesystem traversal. Symlink loops must be detected without holding every visited inode in memory on constrained systems.
- Serialisation and ORMs. Deep-copying an object graph with arbitrary cross-references is Copy List with Random Pointer at production scale — usually with a map, but the constraint version teaches why the map was needed.
5. Problems in this chapter
▶ Linked List Cycle
Determine whether a list contains a loop, in O(1) space. Floyd's tortoise and hare, and the follow-up — where does the loop start — is where the interesting conversation begins.
Pattern: two speeds. Target: O(n) time, O(1) space (versus the O(n)-space hash-set baseline).
▶ Copy List with Random Pointer
Deep-copy a list where each node also points at an arbitrary node. Weave the copies in, resolve the random pointers locally, then unweave.
Pattern: restructure-then-restore. Target: O(n) time, O(1) extra space (versus the O(n)-space map).
6. Common pitfalls 🚫
- Checking only
fastin the loop guard. You needfast and fast.next— the two-step advance can fall off either node. - Starting the pointers one apart "to avoid the immediate match". Start both at the head and advance before comparing; offsetting them makes the entry-point proof stop working.
- Forgetting to unweave. The original list must be returned to its exact prior shape, since the caller still references it.
- Handling
random = Nonecarelessly.node.random.nextdereferences null whenrandomis unset; guard it explicitly. - Assuming the loop starts at the head. The meeting point is not the entry, and the entry is rarely the head — the reset step is required.
- Reaching for a hash set and stopping there. It is a correct O(n)-space answer and the right thing to say first, but the follow-up is always "now do it in O(1)".
7. Key takeaways
- Two pointers at different speeds reveal structure neither could see alone — cycle existence, the middle, the k-th from the end.
- Meeting proves a loop; a reset finds its entry. Know the argument, not just the steps.
- Guard both
fastandfast.next. - Restructuring can replace a hash map. Interleaving makes "the copy of X" reachable as
X.next. - Restore what you rearranged. A temporarily broken input must be intact when you return.
- Why interviewers like it: the code is ten lines, so the entire signal is in your edge-case handling and whether you can explain why the pointers meet where they do.
Order: Linked List Cycle → Copy List with Random Pointer.
Linked List Cycle II
Core idea: A singly linked list is a functional chain — each node points to exactly one successor — so following
nexteither runs off the end (None) or loops forever. Run aslowpointer (+1) and afastpointer (+2): they meet inside the cycle iff a cycle exists. Then comes the elegant part — reset one pointer to the head and advance both by 1; they collide exactly at the cycle's entry node. The reason is pure arithmetic: the distance from the head to the entry equals the distance from the meeting point to the entry. All of it inO(1)extra space.
Problem, rephrased
You're handed the head of a singly linked list. Somewhere a node's next pointer might secretly point back to an earlier node, splicing the tail into a loop — like a train track that, instead of ending at a station, curves around and rejoins itself partway down the line.
Your job (LeetCode 142): return the node where the cycle begins — the first node the tail loops back into. If there is no cycle (the track ends at a buffer, None), return None.
You must not modify the list, and the senior-level expectation is O(1) extra memory.
| Input list | Cycle? | Output | Why |
|---|---|---|---|
3 → 2 → 0 → -4, tail → node 2 |
yes, enters at the 2 node |
node with val 2 |
the -4 node's next points back to 2 |
1 → 2, tail → node 1 |
yes, enters at the head | node with val 1 |
2's next points back to the head |
1, no cycle (next = None) |
no | None |
a single node ending in None |
1 → 2 → 3 → 4, all next forward |
no | None |
the chain terminates at None |
The single move — "advance to node.next" — is the only operation. Everything below is about following next at two speeds and noticing where the pointers land.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson