Problem overview
Each node of the list has a val, a next pointer and a random pointer. The random pointer can point at any node of the same list, or at null. The method copyRandomList receives the head and must return the head of a deep copy: exactly n new nodes with the same values, whose next and random pointers lead only to other new nodes. The original list must be left as it was.
The input is written as pairs [val, random_index]. In [[1, 1], [2, 1]], both random pointers go to the node at index 1. An empty list gives null.
This version of the lesson adds a budget: O(n) time and only O(1) extra space. The copy itself doesn't count as extra space, but a table from each original to its copy would, and that rules out the usual Map solution.
Approach: interleave the copies with the originals
The map solution exists to answer one question: given an original node, where is its copy? The idea here is to make the answer part of the list. Put each copy directly after its original:
A -> A' -> B -> B' -> C -> C'
Now the copy of any original X is simply X.next. That gives three passes:
- Interleave. After each original node, insert a new node with the same value.
- Set the random pointers. For an original
nodewith a random targetR, the copy ofnodeisnode.nextand the copy ofRisR.next. Sonode.next.random = node.random.next. - Separate. Unweave the two lists: restore the originals'
nextpointers and chain the copies together. The caller's list must be exactly as it was.
The order matters. Pass two has to come after pass one, since every copy must exist, and before pass three, because the shortcut only works while the copies are still interleaved.
No extra container is used, only a few variables, so the space is O(1).
Step by step
- Handle the empty list. If
headisnull, returnnull. Later code readshead.next, which would fail otherwise. - Start pass one with
nodeathead, and loop whilenodeis truthy. - Save the original successor in
nextNode. Inserting the copy overwritesnode.next, and without the saved reference the rest of the list would be lost. - Create
copyNodewith the same value. Itsrandomstays unset for now, since not every copy exists yet. - Splice it in: set
node.next = copyNodeandcopyNode.next = nextNode. The chain continues into the rest of the list. For the last original,nextNodeisnull, so the last copy ends the chain. - Jump to
nextNode, skipping the new copy. Moving tonode.nextwould land on the copy and insert copies of copies forever. - Start pass two from
headagain. Inside, takecopyNode = node.next. - Set the random pointer. If
node.randomexists, the copy's random pointer becomesnode.random.next, which is the copy of the target. If it doesn't, the copy'srandomstays as the new node was created. - Jump to
copyNode.next, the next original, so this pass visits originals only. - Save
newHead = head.next, the first copy. Do it before pass three rewireshead.next. - Start pass three from
head. Take the copy afternode, thennextNode, the next original after that copy. - Chain the copies. If
nextNodeexists, setcopyNode.next = nextNode.next, the following copy. For the last pair, the last copy'snextis alreadynull. - Restore the original with
node.next = nextNode, then advancenodetonextNode. - Return
newHead.
The solution in TypeScript
class Solution {
copyRandomList(head: ListNode | null): ListNode | null {
if (!head) {
return null;
}
let node = head;
while (node) {
let nextNode = node.next;
let copyNode = new ListNode(node.val);
node.next = copyNode;
copyNode.next = nextNode;
node = nextNode;
}
node = head;
while (node) {
let copyNode = node.next;
if (node.random) {
copyNode.random = node.random.next;
}
node = copyNode.next;
}
let newHead = head.next;
node = head;
while (node) {
let copyNode = node.next;
let nextNode = copyNode.next;
if (nextNode) {
copyNode.next = nextNode.next;
}
node.next = nextNode;
node = nextNode;
}
return newHead;
}
}
The TypeScript details worth knowing:
- Block-scoped variables.
copyNodeandnextNodeare declared withletinside each loop body, so every pass has its own variables. The same name in three loops is not a clash. - Types are only notes.
nodehas the typeListNodefromhead, yet the loops assignnullto it. With strict mode off, the compiler allows this. Types are erased when TypeScript compiles to JavaScript, so the running code only sees objects andnull. In strict mode you would writelet node: ListNode | null = headand add!where a node is read after a splice. - Truthiness as the loop test.
while (node)andif (node.random)work because an object is truthy andnullis falsy. - New nodes start without a
random. A node created from just a value has no random target yet, which is why pass two sets it only when the original has one.
Complexity
Time: O(n). Each of the three passes visits each original node once, and each visit does a constant number of pointer assignments.
Space: O(1) extra. The n new nodes are the required output. Besides them, the code keeps only a handful of variables. The interleaving is temporary and is undone in pass three.
Edge cases and common mistakes
- An empty list. The first
ifreturnsnullbefore any pointer is read. - A single node. The list becomes
A, A'. If its random points at itself, pass two setsA'.random = A.next, which isA', a pointer to itself. Pass three separates the nodes. - A random pointer to the last node.
node.random.nextis then the last copy, which exists after pass one. - Moving to
node.nextin pass one. This lands on the copy that was just inserted, so the loop copies its own copies and never ends. - Wiring random pointers before the interleaving is complete. The target's copy would not exist yet.
- Not restoring the original. Leaving the copies woven into the input list changes data the caller owns.
- Setting the copy's random to
node.random. This points into the original list, and the copy must not. The pointer must benode.random.next. - Reading
head.nexttoo late. Pass three setshead.nextback to the second original, so the first copy has to be saved first.
Testing
[[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]gives the same shape, with all new nodes.[[1, 1], [2, 1]]gives[[1, 1], [2, 1]].[[3, null], [3, 0], [3, null]]gives[[3, null], [3, 0], [3, null]].- An empty list gives
null.
