Problem overview
The method copyRandomList receives the head of a linked list of ListNode objects. Each node has a value, a next pointer and a random pointer, which may point to any node of the list or to null. The method must build a deep copy: exactly n brand-new nodes with the same values, where the new nodes' next and random pointers lead to new nodes and never to nodes of the original list. It returns the head of the copy, or null for an empty list.
The examples write each node as [val, random_index]. In [[7,null],[13,0],[11,4],[10,2],[1,0]], the node holding 13 has its random pointer on index 0, the node holding 7. The copy must have the same shape: its second node holds 13 and its random pointer goes to the copy's own first node, not the original one. The statement asks for O(n) time and at most O(n) extra space, and the list has up to 1000 nodes.
Approach: map every original node to its copy
Copying values and next links is easy: walk the list and create nodes. The difficulty is the random pointer. A random pointer can point forward or backward, so when you create a copy you may not have created its target yet.
The fix is to separate creation from wiring. First create every copy, remembering which original it belongs to in a map from original node to copy node. After this pass, every original has a copy. Then walk the original list again and, for each node, look up its copy and set the copy's next to the copy of node.next and the copy's random to the copy of node.random. Both lookups work because every copy already exists.
The map's keys must be the nodes themselves, compared by reference. Two different nodes can hold the same value, and a map keyed by value would mix them up.
Step by step
- Create the map.
val copies = scala.collection.mutable.HashMap[ListNode, ListNode]()maps each original to its copy. It is avalbecause the reference to the map never changes, while the map's contents do. The full package name is written because nothing is imported. - Create the walker,
var node = head, soheaditself stays untouched for later. - First pass: loop while
node != null. - Store a copy with
copies(node) = ListNode(node.`val`). Only the value is copied now. The backticks are needed becausevalis a Scala keyword. - Advance with
node = node.next. Without it the loop never ends. - Go back to the start with
node = head, since the first pass leftnodeat the end. - Second pass: loop while
node != nullagain. - Wire
next. The copy'snextisnullwhen the original has no successor, otherwise the copy of that successor. Anif/elseexpression is needed becausecopies(null)would fail. - Wire
randomwith the same kind of expression. - Advance along the original list with
node = node.next. - Return the copy of the head:
if (head == null) null else copies(head). An empty list has no head to look up.
The solution in Scala
class Solution {
def copyRandomList(head: ListNode): ListNode = {
val copies = scala.collection.mutable.HashMap[ListNode, ListNode]()
var node = head
while (node != null) {
copies(node) = ListNode(node.`val`)
node = node.next
}
node = head
while (node != null) {
copies(node).next = if (node.next == null) null else copies(node.next)
copies(node).random = if (node.random == null) null else copies(node.random)
node = node.next
}
if (head == null) null else copies(head)
}
}
The Scala details worth knowing:
- Mutable versus immutable maps. Scala's default
Mapis immutable. The lesson usesscala.collection.mutable.HashMap, whose contents can be changed in place. copies(node) = xisupdate. The assignment syntax callscopies.update(node, x), which adds or replaces the entry.copies(key)throws for a missing key. Applying a map to a key that is absent throwsNoSuchElementException. That is whynullpointers are checked before the lookup.copies.get(key)would return anOptioninstead, andgetOrElsegives a default.- Reference keys. The map hashes and compares its keys with
hashCodeandequals. For a plain class these are identity-based, so each node is its own key, even when two nodes hold the same value. This is what the algorithm needs. A class that overridesequalsby contents would make nodes with equal contents collide. if/elseas an expression. Scala has no ternary operator.if (c) a else bproduces a value, so it can be assigned to a field or returned as the last expression.- Calling the constructor without
new.ListNode(node.`val`)works in Scala 3. - The last expression is the result, so there is no
return.
Complexity
Time: O(n). Each pass visits every node once, and each map insertion or lookup takes constant time on average.
Space: O(n). The map holds one entry per node, and the copies themselves take n new nodes, which the result needs anyway. The statement allows O(n) extra space.
Edge cases and common mistakes
- Empty list. Neither loop runs, and the final
ifreturnsnull. Writingcopies(head)without the check would throw. - A
nullrandom pointer. The first example's first node has none. Looking upnullin the map would throw, so the check comes first. - Random pointing to the node itself or backward. Because all copies exist before any wiring, forward, backward and self pointers are all handled the same way.
- Wiring in the first pass. A copy's target may not exist yet. Two passes avoid that.
- Pointing the copy at an original node. Writing
copies(node).random = node.randomwould leave the copy sharing nodes with the original, which breaks the deep copy. - Equal values.
[[3,null],[3,0],[3,null]]has three nodes holding 3. The map is keyed by node, so each gets its own copy. - Forgetting to reset
node. The second loop would not run at all, becausenodeis alreadynull.
Testing
[[7,null],[13,0],[11,4],[10,2],[1,0]]gives the same list, built from new nodes.[[1,1],[2,1]]gives[[1,1],[2,1]].[[3,null],[3,0],[3,null]]gives[[3,null],[3,0],[3,null]].[]givesnull, the empty list.

