The next pointers alone would make this a plain list copy. The random pointer is what makes it interesting: when you build the copy of node X, its random target Y might not have been created yet (it could be later in the list, or even X itself). You need a way to look up βthe copy of node Yβ regardless of visit order.
Small example used below: A(val=1) β B(val=2) β C(val=3), with A.random β C, B.random β null, C.random β A. Draw it as three nodes in a row; a solid arrow between consecutive nodes is next, and curved labels below mark each random target (A.random β C, C.random β A, B.random β null).
Hash Map (Old Node β New Node)
Time O(n)Space O(n)Make one pass to create a brand-new node for every original node, and store the mapping old_node β new_node in a dictionary. Then make a second pass where each new nodeβs next and random are set by looking up the mapping for the original nodeβs next and random β the dictionary handles the case where the random target has not been βreachedβ yet in list order, since all copies already exist by the second pass.
class Node: def __init__(self, x: int, next: "Node | None" = None, random: "Node | None" = None): self.val = x self.next = next self.random = random
class Solution: def copyRandomList(self, head: "Node | None") -> "Node | None": if not head: return None
old_to_new = {} node = head while node: old_to_new[node] = Node(node.val) node = node.next
node = head while node: old_to_new[node].next = old_to_new.get(node.next) old_to_new[node].random = old_to_new.get(node.random) node = node.next
return old_to_new[head]Correct and easy to follow β the .get(...) returns None automatically when node.next or node.random is None. The only downside is the O(n) dictionary mapping every old node to its copy.
Interweaving (O(1) Extra Space)
OptimalTime O(n)Space O(1)Avoid the hash map by temporarily splicing each copy directly after its original, turning A β B β C into A β A' β B β B' β C β C'. Now the copy of any nodeβs random target is always right next to that target: node.random.next is the copy of node.random. Once every random pointer is set this way, unweave the two interleaved lists back apart.
class Solution: def copyRandomList(self, head: "Node | None") -> "Node | None": if not head: return None
# 1. Interweave: A -> A' -> B -> B' -> ... node = head while node: copy = Node(node.val) copy.next = node.next node.next = copy node = copy.next
# 2. Assign random pointers using the interweaved structure node = head while node: if node.random: node.next.random = node.random.next # copy's random -> copy of node.random node = node.next.next
# 3. Unweave into two separate lists node = head new_head = head.next while node: copy = node.next node.next = copy.next copy.next = copy.next.next if copy.next else None node = node.next
return new_headWhy itβs correct: after interweaving, node.next is always nodeβs own copy β so for any node.random pointing at some original node Y, Yβs copy is exactly node.random.next. This lets every random pointer be resolved without any auxiliary storage, and the final unweave pass cleanly restores both the original list and produces the fully-linked copy. Complexity: three separate O(n) passes (interweave, assign random, unweave), each touching every node a constant number of times β O(n) time. Only a handful of pointer variables are used, and no data structure scales with n β O(1) extra space β optimal, since producing n new nodes with correct pointers requires visiting every original node at least once.