2 minute read

Problem Statement

see Copy List with Random Pointer

Intuition

My initial thoughts are to use a hash map to keep track of the mapping between original nodes and their corresponding copied nodes.

Approach

I’ll traverse the original linked list once and create a copy of each node while storing the mapping in a hash map. Then, I’ll traverse the original list again to set the next and random pointers of the copied nodes based on the hash map.

I’ll use a dummy node to simplify the creation of the new linked list.

Complexity

  • Time complexity: O(n), where n is the number of nodes in the linked list. We traverse the list twice.

  • Space complexity: O(n), as we use a hash map to store the mapping between original and copied nodes. The space complexity is linear with respect to the number of nodes in the linked list.

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]':
        hash_map = defaultdict()
        curr = head
        while curr:
            hash_map[curr] = Node(curr.val)
            curr = curr.next
        
        dummy = Node(-1)
        p = dummy
        curr = head
        while curr:
            p.next = hash_map[curr]
            p = p.next
            if curr.next in hash_map:
                p.next = hash_map[curr.next]
            if curr.random in hash_map:
                p.random = hash_map[curr.random]
            curr = curr.next

        return dummy.next

Editorial Solution

Recursion Approach

class Solution(object):
    """
    :type head: Node
    :rtype: Node
    """
    def __init__(self):
        # Dictionary which holds old nodes as keys and new nodes as its values.
        self.visitedHash = {}

    def copyRandomList(self, head):

        if head == None:
            return None

        # If we have already processed the current node, then we simply return the cloned version of it.
        if head in self.visitedHash:
            return self.visitedHash[head]

        # create a new node
        # with the value same as old node.
        node = Node(head.val, None, None)

        # Save this value in the hash map. This is needed since there might be
        # loops during traversal due to randomness of random pointers and this would help us avoid them.
        self.visitedHash[head] = node

        # Recursively copy the remaining linked list starting once from the next pointer and then from the random pointer.
        # Thus we have two independent recursive calls.
        # Finally we update the next and random pointers for the new node created.
        node.next = self.copyRandomList(head.next)
        node.random = self.copyRandomList(head.random)

        return node

Iterative Approach

class Solution(object):
    def __init__(self):
        # Creating a visited dictionary to hold old node reference as "key" and new node reference as the "value"
        self.visited = {}

    def getClonedNode(self, node):
        # If node exists then
        if node:
            # Check if its in the visited dictionary          
            if node in self.visited:
                # If its in the visited dictionary then return the new node reference from the dictionary
                return self.visited[node]
            else:
                # Otherwise create a new node, save the reference in the visited dictionary and return it.
                self.visited[node] = Node(node.val, None, None)
                return self.visited[node]
        return None

    def copyRandomList(self, head):
        """
        :type head: Node
        :rtype: Node
        """

        if not head:
            return head

        old_node = head
        # Creating the new head node.       
        new_node = Node(old_node.val, None, None)
        self.visited[old_node] = new_node

        # Iterate on the linked list until all nodes are cloned.
        while old_node != None:

            # Get the clones of the nodes referenced by random and next pointers.
            new_node.random = self.getClonedNode(old_node.random)
            new_node.next = self.getClonedNode(old_node.next)

            # Move one step ahead in the linked list.
            old_node = old_node.next
            new_node = new_node.next

        return self.visited[head]