138 Copy List with Random Pointer
Published:
Problem
Solution
Intuition
This problem requires us to return a deep copy of a given linked list. The linked list contains the standard Node.next
for the links; however, there is also a Node.random
property which points to a random node in the list or to None
. We must recreate this list as a deep copy which means a new copy of the same list in memory, while maintaining the previous list.
We can accomplish this either through the use of a hashmap which will have as keys the current node and as its value it’s deep copy. We can then iterate through the list and set up our links by grabbing our copied nodes from memory through the use of our hashmap. This solution has an $O(n)$ time complexity with an $O(n)$ space complexity.
We can do better through interleaving the copied nodes into the old list, and then resetting the links in order to ensure the old list is not affected, while also pulling our created list out of the chain. We can first create the new nodes and set them to be after their counterparts in the linked list. In essence, new_node.next = node.next.next
and node.next = new_node
. This places new_node
between node
and its formerly next node. We can continue this process until we traverse through the whole linked list. Our linked list now contains node1 -> node1_copy -> node2 -> node2_copy … etc.
The next step is to recreate the node.random
property for each node. Because each node’s copy is directly after it, we can simply traverse through the list and for nodes which have the random property (currently only the original nodes), we can set their copy’s random property to be the original node’s random node’s next node. Quite the mouthful, however, in code this is neatly written as:
if curr.random:
curr.next.random = curr.random.next
Our last step is to extract the copy list out of the original list and preserve the original list’s links. We simply need to for each node, set it’s next property to jump over a node since as previously mentioned, our list currently looks like node1 -> node1_copy -> node2 -> node2_copy … etc.
Approach
- Check our base case for if no list exists and return
None
. - Iterate through the list and for each node, create a copy of the node,
new_node
. Setnew_node.next = curr.next
andcurr.next = new_node
to ensure the copied node is inbetween its original counterpart and the original counterpart’s next node. - Iterate through the list and for each node with a random property, set it’s copy’s random node to be the copy of the original node’s random node. Because the copies are always directly after their original counterparts, this is neatly done.
- Separate the copied list out of the original list and reset the links of the original list by making the next property skip over a node to ensure original nodes point to original nodes, and copy nodes point to copy nodes.
- Return the head of the copied list.
Complexity
- Time complexity: $O(n)$. We iterate through the entire list 3 times and perform constant time operations on each iteration.
- Space complexity: $O(1)$. We do not utilize any additional space than the space required for the copy which is part of expected return value.
Code
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return None
curr = head
while curr:
new_node = Node(x=curr.val)
new_node.next = curr.next
curr.next = new_node
curr = curr.next.next
curr = head
while curr:
if curr.random:
curr.next.random = curr.random.next
curr = curr.next.next
curr = head
new_head = head.next
while curr.next:
temp = curr.next
if curr.next:
curr.next = curr.next.next
curr = temp
return new_head