Copy List with Random Pointer is a classic pointer-juggling problem. Each node has an extra random pointer that can point at any node in the list (or None), and you must produce a true deep copy. The elegant solution does it in O(1) extra space with no hash map — by weaving the copies into the original list.
Problem. A linked list of length n is given, where each node has a next pointer and a random
pointer (to any node in the list, or None). Build a deep copy: a brand-new list whose next and
random pointers mirror the original, sharing no nodes with it.
Example: 7 -> 13, where 7.random = 13 and 13.random = 7. The copy must be a separate 7' -> 13'
with 7'.random = 13' and 13'.random = 7'.
The slow way first
The obvious fix is a hash map: first pass, create a clone of every node and store original -> clone. Second pass, for each original node set clone.next = map[original.next] and clone.random = map[original.random]. That works and is O(n) time — but it costs O(n) extra space for the map.
The question to ask: can I find a node's clone without a separate lookup table? If every clone sat in a known, predictable spot relative to its original, I would not need the map at all.
The idea: weave the clones in
Splice each clone right after its original, so the list becomes 7 -> 7' -> 13 -> 13'. Now the clone of any node X is always X.next — that is the lookup table, built into the list itself. Then clone.random = orig.random.next. Finally, unweave the interleaved chain back into two separate lists.
The key insight: by placing each clone at orig.next, the relationship orig.random.next == clone of orig.random holds for free, so we copy random pointers with no auxiliary storage.
Walk through it
Step through the animation. First the clones (bottom color) get spliced in after each original. Then each clone's random pointer is wired using orig.random.next. Finally the chain is unwoven so the original (top) and the copy (bottom) become two clean, independent lists.
Pseudocode
if list is empty: return None
# Pass 1: weave a clone in after each original
cur = head
while cur:
clone = new node with cur.val
clone.next = cur.next
cur.next = clone
cur = clone.next # skip past the clone
# Pass 2: copy random pointers
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next # clone's random
cur = cur.next.next
# Pass 3: unweave into original and copy
cur = head
copy_head = head.next
while cur:
clone = cur.next
cur.next = clone.next
clone.next = clone.next.next if clone.next else None
cur = cur.next
return copy_headThe Python solution
def copy_random_list(head):
if not head:
return None
cur = head
while cur:
clone = Node(cur.val)
clone.next = cur.next
cur.next = clone
cur = clone.next
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next
cur = head
copy_head = head.next
while cur:
clone = cur.next
cur.next = clone.next
clone.next = clone.next.next if clone.next else None
cur = cur.next
return copy_head- Pass 1 (the first
while) creates each clone and splices it in right after its original, so the clone of any nodeXlives atX.next. - Pass 2 is the heart of the trick:
cur.nextis the current clone, andcur.random.nextis the clone ofcur.random. The highlighted line wires the clone'srandomwith no hash map. - The
if cur.randomguard handles nodes whoserandomisNone. - Pass 3 restores each original's
nextand links the clones together, leaving two independent lists. - We return
copy_head, the head of the deep copy (head.nextbefore unweaving).
Complexity
| Case | Time | Notes |
|---|---|---|
| Hash-map copy | O(n) (moderate) | two passes, but O(n) extra space |
| Interweave (this solution) | O(n) (moderate) | three passes, O(1) extra space |
O(1) (fast)Both approaches are O(n) time, but the interweave trick drops the extra space from O(n) to O(1) (ignoring the output list, which we must build either way). That space win is exactly what interviewers are fishing for with this problem.
When this pattern shows up
When you need to associate each original node with its copy (or its "partner") without a hash map,
ask whether you can store that link inside the structure itself — interleaving clones, or temporarily
repurposing a next pointer, are common O(1)-space tricks for linked lists.
Do not forget to restore the original list in the final pass. If you only extract the copy and leave the originals pointing at clones, you have corrupted the input — a frequent bug that fails the hidden tests.
Practice
After weaving, the list reads 7 -> 7' -> 13 -> 13', and 7.random = 13. What should 7'.random be, and how do we compute it?
1. Why does interweaving the clones remove the need for a hash map?
2. In pass 2, how is a clone's random pointer set?
3. What is the extra space used by the interweave solution?
4. Why is the third pass (unweaving) necessary?