Problem overview
The function copyRandomList receives the head of a linked list of *ListNode values. Besides Val and Next, every node has a Random pointer that may point to any node of the list, or be nil. The function must build a deep copy: exactly n brand-new nodes with the same values, where the Next and Random pointers of the copies point to copies, never to original nodes. It returns the head of the copy.
Tests describe a list as pairs [val, random_index]. For [[7,null],[13,0],[11,4],[10,2],[1,0]], the node with value 13 has its random pointer on index 0, the node holding 7. The copy must have the same shape, with its own 13 pointing to its own 7. An empty list yields nil. The statement allows O(n) time and up to O(n) extra space.
Approach: a map from each original to its copy
Copying the values and the Next chain is easy. The hard part is Random: a node's random target can be earlier or later in the list, so when the copy of that node is created, the copy of its target may not exist yet.
The fix is to split the work into two passes, with a map linking each original node to its copy:
- First pass: create a copy of every node, holding only the value, and record it in the map under the original node as the key.
- Second pass: now every original has a copy, so any pointer can be translated by a lookup. For each original node, set the copy's
Nextto the copy ofnode.Nextand itsRandomto the copy ofnode.Random.
The map has type map[*ListNode]*ListNode. Pointer keys compare by address, so two different nodes with the same value are different keys.
The nil cases come out right without any if. The map holds no entry for nil, and reading a missing key from a Go map gives the zero value of the value type, which for a pointer is nil. So copies[node.Next] is nil for the last node, and copies[node.Random] is nil when there is no random target.
Step by step
- Create the map with
make(map[*ListNode]*ListNode), namedcopies. A map must be created before use: writing to anilmap panics. - Declare
nodeand start it athead, soheadstays available for the second pass and the return value. - Loop while
node != nil. Go has nowhile; aforwith a condition does the same. - Store a new node:
copies[node] = &ListNode{Val: node.Val}. The literal sets onlyVal, soNextandRandomstart asnil. They are filled in later, when all copies exist. - Advance with
node = node.Next. Without this line the loop never ends. - Reset with
node = headfor the second pass. - Loop again while
node != nil. - Wire
Next:copies[node].Next = copies[node.Next]. - Wire
Random:copies[node].Random = copies[node.Random]. The order of the two lines doesn't matter, because both only read the map. - Advance along the original list, with
node = node.Next. The pass walks the originals while it builds the copies' links. - Return
copies[head], the copy of the head. For an empty listheadisnil, the lookup finds no entry, and the result isnil.
The solution in Go
func copyRandomList(head *ListNode) *ListNode {
copies := make(map[*ListNode]*ListNode)
node := head
for node != nil {
copies[node] = &ListNode{Val: node.Val}
node = node.Next
}
node = head
for node != nil {
copies[node].Next = copies[node.Next]
copies[node].Random = copies[node.Random]
node = node.Next
}
return copies[head]
}
Go details worth knowing:
- Missing keys give the zero value. Reading
copies[k]for a key that isn't in the map returnsnilhere and doesn't panic. When you need to tell "absent" from "present with the zero value", usev, ok := copies[k]. This solution doesn't need it, because the absent key and anilpointer mean the same thing. - Assigning to a field of a map value.
copies[node].Next = ...compiles because the map's values are pointers: the expressioncopies[node]is a*ListNode, and its field can be assigned through the pointer. With a map of struct values (not pointers), Go would reject such an assignment. - Reading a nil map is fine, writing is not. A
nilmap returns zero values on reads, butcopies[node] = ...on one panics withassignment to entry in nil map. That is whymakecomes first. - Reusing a variable. The second pass assigns
node = headto the variable that the first pass left atnil. It is a new walk, not a new declaration.
Complexity
Time: O(n). Each pass visits every node once, and each map operation takes constant time on average.
Space: O(n). The map holds one entry per node, on top of the n new nodes that the problem requires anyway. The copy itself is unavoidable. The extra cost of the approach is the map, and the other lesson on this problem removes it.
Edge cases and common mistakes
- Empty list. The loops don't run, and
copies[head]isnil. - Random pointer to itself or to a later node. The two-pass structure handles both, since all copies exist before any pointer is wired. A single pass that creates copies on demand would need extra care here.
- Random is
nil.copies[nil]isnil, so the copy'sRandomstaysnil. - Equal values. For
[[3,null],[3,0],[3,null]], the three nodes all hold 3, but they are different keys, so each random pointer lands on the correct copy. Keying the map by value would break this. - Sharing nodes with the original. Copying a pointer, such as
copies[node].Random = node.Random, leaves the copy pointing into the original list, which the statement forbids. - Forgetting
node = head. After the first loopnodeisnil, so the second loop would do nothing and the copy would have no links.
Testing
[[7,null],[13,0],[11,4],[10,2],[1,0]]gives the same description, 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]].[]gives[].
