Problem Statement
A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null.
Construct a deep copy of the list. The deep copy should consist of exactly n brand new nodes, where each new node has its value set to the value of its corresponding original node. Both the next and random pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. None of the pointers in the new list should point to nodes in the original list.
Return the head of the copied linked list.
The linked list is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index], where:
val: an integer representing Node.val
random_index: the index of the node (range 0 to n-1) that the random pointer points to, or null if it does not point to any node.
Additional information
0 <= n <= 1000
-10^4 <= Node.val <= 10^4
Node.random is null or is pointing to some node in the linked list.
Example 1:
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
Example 2:
Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]
Example 3:
Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]
Brute Force (Hash Map)
Intuition
The difficulty in copying this list is that the random pointer might point to a node that hasn't been created yet.
To solve this, we can use a Hash Map (Dictionary) to map every Old Node to its corresponding New Node.
We make two passes:
- First Pass: Create a new node for every node in the original list and store the mapping
old_node -> new_node in the hash map.
- Second Pass: Iterate through the list again. Use the hash map to connect the
next and random pointers for the new nodes.
Algorithm
- Initialize a hash map
old_to_new with { None: None }.
- Iterate through the list (
curr). Create a copy Node(curr.val) and store it: old_to_new[curr] = copy.
- Iterate through the list again. For every
curr, set:
copy.next = old_to_new[curr.next]
copy.random = old_to_new[curr.random]
- Return
old_to_new[head].
"""
# 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
"""
def copy_random_list(head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return None
old_to_new = {None: None}
curr = head
while curr:
old_to_new[curr] = Node(curr.val)
curr = curr.next
curr = head
while curr:
copy = old_to_new[curr]
copy.next = old_to_new[curr.next]
copy.random = old_to_new[curr.random]
curr = curr.next
return old_to_new[head]
Time & Space Complexity
Interweaving (Optimal Space)
Intuition
We can optimize the space by avoiding the hash map. Instead of storing the mapping externally, we can store it "inside" the list itself by interweaving the copied nodes with the original nodes.
Structure: A -> A' -> B -> B' -> C -> C'.
A.next is A' (the copy).
A'.next is B (the next original node).
This allows us to find the copy of any node N simply by accessing N.next.
Algorithm
- Interweave: Iterate through the list. For each node
curr, create copy. Insert copy between curr and curr.next.
curr.next = copy, copy.next = old_next.
- Set Random: Iterate again. If
curr.random exists, set curr.next.random = curr.random.next. (Since curr.random.next is the copy of the random node).
- Separate: Restore the original list and extract the copied list.
curr.next = curr.next.next
copy.next = copy.next.next
def copy_random_list(head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return None
# 1. Interweave the list with copies
curr = head
while curr:
new_node = Node(curr.val, curr.next)
curr.next = new_node
curr = new_node.next
# 2. Assign random pointers
curr = head
while curr:
if curr.random:
curr.next.random = curr.random.next
curr = curr.next.next
# 3. Separate the lists
curr = head
new_head = head.next
while curr:
copy = curr.next
curr.next = copy.next
if copy.next:
copy.next = copy.next.next
curr = curr.next
return new_head
Time & Space Complexity
Time complexity: O(N)
Reason: We iterate through the list three times (Interweave, Link Random, Separate).
Space complexity: O(1)
Reason: We only use a constant amount of extra space (excluding the result list itself).