Clone a Linked List with Random Pointers: Deep Dive with All 3 Approaches
- Two-pass hash map: first create all new nodes, then set next and random. O(n) time, O(n) space.
- O(1) space via weaving: interleave clones into original, set randoms, then un-weave.
- The hash map key is the original node pointer; the value is the cloned node.
- Core challenge: a copied pointer still points into the original structure. You must remap every pointer to the corresponding clone.
- HashMap approach (O(n) space): two passes — first create all clones, then wire next/random using the map.
- Weaving approach (O(1) space): interleave clones between originals, set randoms via A.random.next, then un-weave.
- Forward reference problem: when processing node A, A.random's clone may not exist yet. HashMap or weaving solves this.
- Production risk: shallow copy (pointing random at original nodes) causes silent data corruption when the original is mutated.
- This is a graph-traversal problem disguised as a linked-list problem — the random pointer breaks linearity.
Original and clone share state — mutation in one affects the other.
jcmd <pid> GC.heap_dump /tmp/heap.hprofjmap -histo <pid> | grep Node (count Node instances — should be 2x original)NullPointerException in cloned list when accessing random pointers.
jcmd <pid> Thread.print (confirm stack trace location)grep -A5 'random' src/ (find all random pointer assignments)Heap grows after cloning but original should be GC-eligible.
jcmd <pid> GC.heap_dump /tmp/heap.hprofjmap -dump:format=b,file=/tmp/heap.hprof <pid>Production Incident
Production Debug GuideSymptom-driven investigation paths for clone-related incidents.
Deep-copying a linked list with random pointers is a pointer-remapping problem. Each node has two outgoing references: next (sequential) and random (arbitrary). A naive copy creates new nodes but leaves random pointers pointing at the original structure — a shallow copy that corrupts when the original is mutated.
The random pointer breaks the list's linearity. When processing node A, its random target (node 7) may not have been cloned yet. Two strategies solve this: a HashMap that pre-creates all clones before wiring pointers, or an interweaving trick that places each clone adjacent to its original so the clone is always reachable via original.next.
This problem appears in serialization of graph-like structures, deep-copying editor undo/redo trees, and duplicating compiler adjacency representations. In production, a shallow copy bug manifests as cross-contamination between independent data structures — one structure's mutation silently affects another.
What is Clone a Linked List with Random Pointers? — Plain English
Each node in this linked list has a next pointer (to the following node) and a random pointer (to any node in the list, or None). Cloning it naively requires two passes: one to create new nodes, and a second to set random pointers. A hash map from original_node -> cloned_node makes both passes easy and is the standard O(n) time, O(n) space approach.
The key challenge: when you create a new node and want to set its random pointer, the target of the random pointer may not have been created yet. The hash map solves this by building all new nodes first, then linking them.
- Photocopying a seating chart copies names in order — but the 'best friend' arrows still point at the original students.
- You need a name-to-copy mapping so every arrow can be redirected to the photocopy.
- Without the mapping, the photocopy is a shallow copy — it shares state with the original.
- The HashMap is that name-to-copy mapping. The weaving trick achieves the same without explicit storage.
How Clone a Linked List with Random Pointers Works — Step by Step) time, O(n) space): 1. Pass 1 — create all new nodes: For each node in the original list: map[original] = Node(original.val). 2. Pass 2 — set pointers: For each node in the original list: map[node].next = map[node.next] (or None if node.next is None). map[node].random = map[node.random] (or None). 3. Return map[head]. O(1) space approach (weaving): 1. Insert each cloned node right after its original: A -> A' -> B -> B' -> ... 2. Set random pointers: A'.random = A.random.next (the clone of A's random). 3. Un-weave: restore original list and extract cloned list.
Hash map, making it unsafe for concurrent access.
- After interleaving, every clone is exactly one hop from its original: original.next = clone.
- So clone.random = original.random.next — the clone of the random target is always its next node.
- No HashMap needed — the interleaved structure IS the mapping.
- The un-weave step restores both lists by separating odd-positioned (original) and even-positioned (clone) nodes.
Worked Example — Tracing the Algorithm
List: 1 -> 2 -> 3 -> None. Random pointers: 1.random=3, 2.random=1, 3.random=2.
Pass 1 — create map: map[1] = Node(1), map[2] = Node(2), map[3] = Node(3).
Pass 2 — set pointers: map[1].next = map[2]. map[1].random = map[3]. map[2].next = map[3]. map[2].random = map[1]. map[3].next = None. map[3].random = map[2].
Cloned list: 1'->2'->3'. 1'.random=3', 2'.random=1', 3'.random=2'. Fully independent deep copy.
- Print System.identityHashCode(original) and System.identityHashCode(clone.random).
- If they match, clone.random points at the original — shallow copy bug.
- If clone.random's hash matches map.get(original.random)'s hash, the copy is correct.
- This works because identity hash is based on memory address, not value.
Implementation
The implementation uses two passes. In the first pass, iterate the original list and create a new node for each original node, storing the mapping in a hash map. In the second pass, use the hash map to wire up the next and random pointers on each new node. This runs in O(n) time and O(n) space. The alternative O(1) space approach interleaves cloned nodes between original nodes, sets random pointers, then splits the two lists apart in a third pass.
package io.thecodeforge.list; import java.util.HashMap; import java.util.Map; /** * Deep clone a linked list with random pointers. * HashMap approach: O(n) time, O(n) space. * Weaving approach: O(n) time, O(1) space. */ public class CloneLinkedListWithRandom { static class Node { int val; Node next; Node random; Node(int val) { this.val = val; this.next = null; this.random = null; } } // ─── Approach 1: HashMap (O(n) space) ────────────────────────────────── public static Node cloneWithHashMap(Node head) { if (head == null) return null; // Pass 1: create all clone nodes, store mapping original -> clone Map<Node, Node> cloneMap = new HashMap<>(); Node current = head; while (current != null) { cloneMap.put(current, new Node(current.val)); current = current.next; } // Pass 2: wire next and random pointers using the map current = head; while (current != null) { cloneMap.get(current).next = cloneMap.get(current.next); cloneMap.get(current).random = cloneMap.get(current.random); current = current.next; } return cloneMap.get(head); } // ─── Approach 2: Weaving (O(1) space) ────────────────────────────────── public static Node cloneWithWeaving(Node head) { if (head == null) return null; // Step 1: Interleave — insert clone after each original // Original: A -> B -> C // After: A -> A' -> B -> B' -> C -> C' Node current = head; while (current != null) { Node clone = new Node(current.val); clone.next = current.next; current.next = clone; current = clone.next; } // Step 2: Set random pointers // A'.random = A.random.next (the clone of A's random target) current = head; while (current != null) { if (current.random != null) { current.next.random = current.random.next; } current = current.next.next; } // Step 3: Un-weave — separate original and clone lists Node cloneHead = head.next; current = head; while (current != null) { Node clone = current.next; current.next = clone.next; clone.next = (clone.next != null) ? clone.next.next : null; current = current.next; } return cloneHead; } // ─── Verification ────────────────────────────────────────────────────── public static boolean verifyClone(Node original, Node clone) { // Verify no original nodes are reachable from the clone // by checking identity hashes don't overlap Node origCurrent = original; Node cloneCurrent = clone; while (origCurrent != null && cloneCurrent != null) { if (origCurrent == cloneCurrent) return false; // same object if (cloneCurrent.random != null && cloneCurrent.random == origCurrent) return false; origCurrent = origCurrent.next; cloneCurrent = cloneCurrent.next; } return cloneCurrent == null && origCurrent == null; // same length } public static void main(String[] args) { // Build: 1 -> 2 -> 3 // Random: 1->3, 2->1, 3->2 Node n1 = new Node(1); Node n2 = new Node(2); Node n3 = new Node(3); n1.next = n2; n2.next = n3; n1.random = n3; n2.random = n1; n3.random = n2; // Clone with HashMap Node clone1 = cloneWithHashMap(n1); System.out.println("HashMap clone:"); System.out.println(" Values: " + clone1.val + " -> " + clone1.next.val + " -> " + clone1.next.next.val); System.out.println(" Random: 1->" + clone1.random.val + ", 2->" + clone1.next.random.val + ", 3->" + clone1.next.next.random.val); System.out.println(" Independent: " + verifyClone(n1, clone1)); System.out.println(" Identity diff: " + (clone1 != n1 && clone1.random != n1.random)); // Clone with weaving Node clone2 = cloneWithWeaving(n1); System.out.println("\nWeaving clone:"); System.out.println(" Values: " + clone2.val + " -> " + clone2.next.val + " -> " + clone2.next.next.val); System.out.println(" Random: 1->" + clone2.random.val + ", 2->" + clone2.next.random.val + ", 3->" + clone2.next.next.random.val); System.out.println(" Independent: " + verifyClone(n1, clone2)); // Edge case: null list Node nullClone = cloneWithHashMap(null); System.out.println("\nNull list clone: " + (nullClone == null ? "null" : "ERROR")); // Edge case: single node with self-random Node self = new Node(42); self.random = self; Node selfClone = cloneWithHashMap(self); System.out.println("Self-random clone: val=" + selfClone.val + ", random->val=" + selfClone.random.val + ", isSameObject=" + (selfClone == selfClone.random)); } }
Values: 1 -> 2 -> 3
Random: 1->3, 2->1, 3->2
Independent: true
Identity diff: true
Weaving clone:
Values: 1 -> 2 -> 3
Random: 1->3, 2->1, 3->2
Independent: true
Null list clone: null
Self-random clone: val=42, random->val=42, isSameObject=true
| Aspect | HashMap (Two-Pass) | Weaving (Interleave) | Recursive Memoised |
|---|---|---|---|
| Time Complexity | O(n) | O(n) | O(n) |
| Space Complexity | O(n) — HashMap storage | O(1) — no extra data structure | O(n) — call stack + memo map |
| Mutates Original? | No | Yes — temporarily inserts clone nodes | No |
| Thread-Safe for Shared Original? | Yes — original is read-only | No — original is modified during clone | Yes — original is read-only |
| Code Complexity | Simple — two clean passes | Moderate — three passes with interleaving | Moderate — recursion with memoisation |
| Handles Cycles in Random? | Yes — never traverses random | Yes — never traverses random | Requires memo map to avoid infinite recursion |
| Handles Null Random? | Yes — map.get(null) returns null | Yes — with null guard | Yes — base case handles null |
| Stack Overflow Risk | None | None | Yes — deep recursion on long lists |
| Debuggability | Easy — map contents are inspectable | Harder — interleaved structure is confusing | Moderate — recursion trace helps |
| Interview Preference | Expected as first answer | Expected as O(1) space follow-up | Acceptable but rarely asked |
🎯 Key Takeaways
- Two-pass hash map: first create all new nodes, then set next and random. O(n) time, O(n) space.
- O(1) space via weaving: interleave clones into original, set randoms, then un-weave.
- The hash map key is the original node pointer; the value is the cloned node.
- Always check for None before dereferencing next and random to avoid NullPointerException.
- This problem tests deep vs shallow copy understanding and pointer manipulation.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the difference between a deep copy and a shallow copy in this context?
- QHow does the O(1) space weaving approach work?
- QWhat happens if you try to copy next and random pointers in a single pass without a hash map?
- QWalk me through the weaving approach step by step. Why does A'.random = A.random.next work?
- QHow would you verify that a clone is truly independent of the original?
- QWhat happens if the random pointers form a cycle? Does either approach break?
- QHow would you extend this to clone a graph (not just a linked list) where each node has arbitrary neighbor pointers?
- QIf the original list is concurrently accessed by other threads, which approach is safe and why?
Frequently Asked Questions
What is the O(1) space approach?
Weave cloned nodes into the original list: A->A'->B->B'->C->C'. Set random: A'.random = A.random.next. Unweave: restore A->B->C and build A'->B'->C'. No hash map needed — the interleaved structure acts as the map. O(n) time, O(1) extra space.
Why can't you just copy next and random in one pass?
When you process node A and want to set A'.random to point to the clone of A.random, that clone might not exist yet. You need either a hash map (pre-create all clones first) or the weaving trick (interleave so that each node's clone is always its immediate next).
Does this algorithm work if random pointers form cycles?
Yes. The hash map approach creates the mapping first, then sets pointers. Cycles in random pointers don't cause infinite loops because we only traverse via the next pointer in both passes, not via random.
How is this different from cloning a plain linked list?
A plain linked list has only next pointers. Cloning it is a single-pass operation — create each clone and immediately set its next. The random pointer adds a second relationship that may point to any node (including forward references), requiring the HashMap or weaving technique.
Can Java's clone() method handle this?
No. Java's Object.clone() performs a shallow copy by default — it copies field references, not the objects they point to. For a linked list with random pointers, clone() would create new node objects but their next and random fields would still point to the original nodes. You must implement deep copy manually.
How do you verify a clone is correct?
Check three things: (1) the clone has the same values in the same order, (2) the clone's random pointers point to the correct clone nodes (not originals), and (3) no original node is reachable from the clone. Use System.identityHashCode to verify object identity.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.