lists row 1 = original · row 2 = copies (′) · solid arrow = next · dashed curve underneath = random
oldToCopy hash map · #i = the node at index i
public Node copyRandomList(Node head) { Map<Node, Node> oldToCopy = new HashMap<>(); oldToCopy.put(null, null); Node cur = head; while (cur != null) { // pass 1: clone every node Node copy = new Node(cur.val); oldToCopy.put(cur, copy); cur = cur.next; } cur = head; while (cur != null) { // pass 2: wire the clones Node copy = oldToCopy.get(cur); copy.next = oldToCopy.get(cur.next); copy.random = oldToCopy.get(cur.random); cur = cur.next; } return oldToCopy.get(head);}
- pass
- —
- cur
- —
- copy
- —
- map size
- —
Why it works
- Brute force fails on random. Copying node by node, a random pointer can aim at a node that has no copy yet, or at one of several nodes with the same value. Looking the target up by walking the list for each node costs O(n²).
- The map turns "which copy?" into a lookup. Pass 1 makes every clone and records
old → copy. In pass 2 every target, wherever it is, already has a clone, sooldToCopy.get(cur.random)answers in O(1). - Two linear passes: O(n) time. The map holds n + 1 entries: O(n) extra space on top of the copy itself.
- Pitfall: null pointers. Seeding the map with
oldToCopy.put(null, null)letscur.nextandcur.randombenullwithout anif. AHashMapaccepts a null key; aTreeMaporConcurrentHashMapwould throwNullPointerExceptionon it. - Pitfall: never point into the old list.
copy.random = cur.randompasses a shallow copy on small tests but shares nodes with the input; every pointer must go through the map. - Follow-up, O(1) extra space: weave each clone right after its original (A → A′ → B → B′), set
A′.random = A.random.next, then unweave. Clone Graph (133) uses the same old-to-copy map on a graph.