-
Notifications
You must be signed in to change notification settings - Fork 0
069 — Copy List with Random Pointer
LeetCode 138 · Medium. A linked list is given where each node contains an additional random pointer, which could point to any node in the list or to nil. Construct a deep copy of the list — the copy must consist of exactly n brand-new nodes, each with its own val, next pointing into the copied list, and random also pointing into the copied list, never back into the original.
Picture photocopying a chain of sticky notes where each note also has a piece of string tied to some other note in the chain (maybe itself, maybe none). Copying the note itself and its position in the chain is easy — walk down the line and copy each one in order. The hard part is the string: when you copy note 5's string, it points to "the original note 12," but you need your copy of note 5's string to point to your copy of note 12 — and you might not have made that copy yet. You need some way to instantly answer "given an original note, which copy did I make of it?" — that's a lookup problem, not a walking problem.
"Deep copy" plus "random pointer that can point anywhere in the list" is the cue. Whenever a copy needs to preserve arbitrary cross-references between nodes (not just the linear next chain), you need a fast way to map "original node" to "its clone" — that's a hash map problem at its core, not a pointer-arithmetic one. Recognize this shape whenever a structure has any pointer besides the one that defines its natural traversal order.
Two passes with a hash map from original node identity to its clone: first create every clone (values only), then wire up next and random on each clone by looking up the originals' targets in the map.
class Node {
var val: Int
var next: Node?
var random: Node?
init(_ val: Int) {
self.val = val
self.next = nil
self.random = nil
}
}
func copyRandomListBruteForce(_ head: Node?) -> Node? {
guard let head = head else { return nil }
var map: [ObjectIdentifier: Node] = [:]
// Pass 1: create every clone, keyed by the original node's identity.
var curr: Node? = head
while let node = curr {
map[ObjectIdentifier(node)] = Node(node.val)
curr = node.next
}
// Pass 2: wire up next and random using the map.
curr = head
while let node = curr {
let clone = map[ObjectIdentifier(node)]
clone?.next = node.next.flatMap { map[ObjectIdentifier($0)] }
clone?.random = node.random.flatMap { map[ObjectIdentifier($0)] }
curr = node.next
}
return map[ObjectIdentifier(head)]
}
// smoke test: A(7) -> B(13) -> C(11), A.random = nil, B.random = A, C.random = A
let a = Node(7); let b = Node(13); let c = Node(11)
a.next = b; b.next = c
b.random = a; c.random = a
let clonedA = copyRandomListBruteForce(a)
print(clonedA?.val ?? -1, clonedA?.next?.val ?? -1, clonedA?.next?.next?.val ?? -1) // 7 13 11
print(clonedA?.random == nil, clonedA?.next?.random === clonedA, clonedA?.next?.next?.random === clonedA) // true true true
print(clonedA !== a, clonedA?.next !== b) // true true — genuinely new nodesBig-O: O(n) time — two linear passes. O(n) extra space for the hash map (in addition to the O(n) for the output copy, which any correct solution needs).
Weave each clone directly after its original (A -> A' -> B -> B' -> ...), use that interleaving to set random pointers in O(1) per node, then un-weave the two lists apart — no hash map required.
func copyRandomList(_ head: Node?) -> Node? {
guard let head = head else { return nil }
// 1. Weave: insert each clone directly after its original.
var curr: Node? = head
while let node = curr {
let clone = Node(node.val)
clone.next = node.next
node.next = clone
curr = clone.next
}
// 2. Set random pointers on the clones: a node's random clone is always
// that random target's very next neighbor (its interleaved copy).
curr = head
while let node = curr {
node.next?.random = node.random?.next
curr = node.next?.next
}
// 3. Unweave: split the interleaved list back into original and clone lists.
curr = head
let clonedHead = head.next
while let node = curr {
let clone = node.next
node.next = clone?.next
clone?.next = clone?.next?.next
curr = node.next
}
return clonedHead
}
// smoke test: A(7) -> B(13) -> C(11), A.random = nil, B.random = A, C.random = A
let x = Node(7); let y = Node(13); let z = Node(11)
x.next = y; y.next = z
y.random = x; z.random = x
let cloned = copyRandomList(x)
print(cloned?.val ?? -1, cloned?.next?.val ?? -1, cloned?.next?.next?.val ?? -1) // 7 13 11
print(cloned?.random == nil, cloned?.next?.random === cloned, cloned?.next?.next?.random === cloned) // true true true
print(x.next === y, y.next === z) // true true — original list is fully restored
// smoke test: single node with a self-loop random pointer
let selfNode = Node(42)
selfNode.random = selfNode
let clonedSelf = copyRandomList(selfNode)
print(clonedSelf?.random === clonedSelf, clonedSelf !== selfNode) // true trueBig-O: O(n) time — three linear passes (weave, wire randoms, unweave). O(1) extra space beyond the output clones themselves — no hash map needed.
This problem leans more on the hash map than on any named traversal pattern — the entire brute force is "remember the original-to-clone mapping so a random pointer copied later can find the right target," which is exactly what a hash map is for. The optimal O(1)-space alternative sidesteps the hash map entirely by using the list's own structure as the lookup table: by physically placing each clone immediately after its original, "the clone of node X" always has a fixed, computable location — X.next — so node.random?.next answers "what's the clone of my random target?" without ever consulting a map. This is the closest fit among the 17 named patterns to paired pointer walking (Two Pointers), since the weave/unweave passes move two logically distinct pointers (original-list pointer and clone-list pointer) through the structure together — but it's a stretch to call it a clean instance of the pattern; the real insight is structural (interleaving), not comparative pointer racing.
-
Linked Lists — the underlying data structure, and the source of the
O(1)splice/relink operations both approaches depend on. -
Hash Maps & Hash Sets — the primary technique behind the brute force: original-node-identity to clone-node lookup in
O(1)average time. - Two Pointers — a stretch here, called out explicitly above; the closest fit among the named patterns is the paired original/clone pointer walking in the weave-and-unweave optimal approach.
Copy List with Random Pointer is photocopying sticky notes with strings attached — either keep a lookup table of "original note → its copy," or copy each note right next to its original so the copy's address is always one step away.
In the optimal weaving approach, step 2 sets node.next?.random = node.random?.next. Explain in your own words why node.random?.next is guaranteed to be the clone of node.random, and not some unrelated node — what invariant does step 1 (the weave) establish that makes this true for every node, including one whose random is nil?
⬅️ Previous: Remove Nth Node From End of List · Next: Add Two Numbers ➡️