138. Copy List with Random Pointer
Problem Description
You are given a linked list where each node has two pointers:
- A
next
pointer that points to the next node in the list (ornull
if it's the last node) - A
random
pointer that can point to any node in the list (ornull
)
Your task is to create a deep copy of this linked list. A deep copy means:
- Create
n
brand new nodes (wheren
is the length of the original list) - Each new node should have the same value as its corresponding original node
- The
next
andrandom
pointers in the copied list should maintain the same structure as the original list, but must point to the new copied nodes, not the original ones
For example, if in the original list node X
has X.random
pointing to node Y
, then in the copied list, the corresponding node x
should have x.random
pointing to the corresponding copied node y
.
The solution uses a hash table approach:
- First pass: Traverse the original list, create a copy of each node, and store the mapping between original nodes and their copies in a hash table
d
. During this pass, also connect thenext
pointers of the copied nodes. - Second pass: Traverse the original list again and use the hash table to set up the
random
pointers for the copied nodes. For each original node, find its copy in the hash table, then set the copy'srandom
pointer to point to the copy of the original node'srandom
target.
The function returns the head of the newly created deep copy of the linked list.
Intuition
The main challenge in this problem is handling the random
pointers. When we're creating a copy of a node, its random
pointer might reference a node that we haven't created yet, or it might reference a node we've already passed.
Consider what happens if we try to copy the list in a single pass: When we copy node A, and A's random
pointer points to node C (which comes later in the list), we can't set up this connection yet because node C's copy doesn't exist. Similarly, if A's random
pointer points backwards to a previously copied node, we need a way to find that copied node.
This naturally leads us to think about maintaining a mapping between original nodes and their copies. A hash table is perfect for this - it gives us O(1)
access to find any copied node given its original.
The solution becomes clear: we need two passes through the list:
- First pass: Create all the new nodes and establish the mapping. While we're at it, we can connect the
next
pointers since they follow a sequential pattern. - Second pass: Now that all nodes are created and we have our mapping, we can properly set up the
random
pointers. For each original node, we look up its copy, then set the copy'srandom
pointer to point to the copy of wherever the original'srandom
points.
The key insight is that we must separate node creation from random
pointer assignment. The hash table serves as the bridge between the original and copied lists, allowing us to translate any reference from the original list to its corresponding reference in the copied list.
Solution Approach
The implementation uses a hash table to maintain the mapping between original nodes and their copies. Here's the step-by-step walkthrough:
Initialization:
- Create a dummy head node
dummy
to simplify list construction - Use a
tail
pointer initialized todummy
to track where to append new nodes - Initialize an empty dictionary
d
to store the mapping between original and copied nodes - Set
cur
pointer to the original list's head
First Pass - Node Creation and Next Pointer Setup:
while cur:
node = Node(cur.val) # Create a new node with same value
tail.next = node # Connect to the previous copied node
tail = tail.next # Move tail to the newly created node
d[cur] = node # Store mapping: original -> copy
cur = cur.next # Move to next original node
During this pass:
- We traverse the original list once
- For each original node, we create a corresponding new node with the same value
- We build the
next
pointer chain for the copied list - We populate the hash table
d
where keys are original nodes and values are their copies
Second Pass - Random Pointer Setup:
cur = head # Reset to start of original list
while cur:
d[cur].random = d[cur.random] if cur.random else None
cur = cur.next
During this pass:
- We traverse the original list again
- For each original node, we access its copy via
d[cur]
- We set the copy's
random
pointer by looking up where the original'srandom
points - If
cur.random
exists, we used[cur.random]
to get the corresponding copied node - If
cur.random
isNone
, we set the copy'srandom
toNone
as well
Return the Result:
- Return
dummy.next
, which is the head of the deep copied list
The time complexity is O(n)
where n
is the number of nodes (two passes through the list), and the space complexity is O(n)
for the hash table storing the node mappings.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with 3 nodes to illustrate the solution approach.
Original List:
Node 1 (val=7) -> Node 2 (val=13) -> Node 3 (val=11) -> null Random pointers: - Node 1.random -> null - Node 2.random -> Node 1 - Node 3.random -> Node 2
Step-by-step execution:
Initialization:
- Create
dummy
node (helper node for building the copy) tail = dummy
(points to where we'll append next)d = {}
(empty hash table)cur = head
(points to Node 1)
First Pass - Creating nodes and setting next pointers:
Iteration 1: cur
points to Node 1 (val=7)
- Create new Node 1' with val=7
tail.next = Node 1'
(dummy -> Node 1')tail = Node 1'
d[Node 1] = Node 1'
(store mapping)cur = cur.next
(move to Node 2)
Iteration 2: cur
points to Node 2 (val=13)
- Create new Node 2' with val=13
tail.next = Node 2'
(Node 1' -> Node 2')tail = Node 2'
d[Node 2] = Node 2'
(store mapping)cur = cur.next
(move to Node 3)
Iteration 3: cur
points to Node 3 (val=11)
- Create new Node 3' with val=11
tail.next = Node 3'
(Node 2' -> Node 3')tail = Node 3'
d[Node 3] = Node 3'
(store mapping)cur = cur.next
(becomes null, exit loop)
After First Pass:
- Copied list structure: Node 1' -> Node 2' -> Node 3' -> null
- Hash table
d
:{ Node 1 -> Node 1', Node 2 -> Node 2', Node 3 -> Node 3' }
Second Pass - Setting random pointers:
Iteration 1: cur
points to Node 1
cur.random
is null- Set
d[Node 1].random = None
(Node 1'.random = null) cur = cur.next
(move to Node 2)
Iteration 2: cur
points to Node 2
cur.random
points to Node 1- Set
d[Node 2].random = d[Node 1]
(Node 2'.random = Node 1') cur = cur.next
(move to Node 3)
Iteration 3: cur
points to Node 3
cur.random
points to Node 2- Set
d[Node 3].random = d[Node 2]
(Node 3'.random = Node 2') cur = cur.next
(becomes null, exit loop)
Final Result: The deep copied list has the structure:
Node 1' (val=7) -> Node 2' (val=13) -> Node 3' (val=11) -> null Random pointers: - Node 1'.random -> null - Node 2'.random -> Node 1' - Node 3'.random -> Node 2'
The function returns dummy.next
, which is Node 1', the head of our deep copied list. Notice how all the pointers in the copied list point to copied nodes, not the original ones, achieving a true deep copy.
Solution Implementation
1"""
2# Definition for a Node.
3class Node:
4 def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
5 self.val = int(x)
6 self.next = next
7 self.random = random
8"""
9
10from typing import Optional
11
12
13class Solution:
14 def copyRandomList(self, head: Optional['Node']) -> Optional['Node']:
15 # Handle edge case: empty list
16 if not head:
17 return None
18
19 # Dictionary to map original nodes to their corresponding copied nodes
20 original_to_copy = {}
21
22 # Create dummy node to simplify list construction
23 dummy = Node(0)
24 tail = dummy
25
26 # First pass: create all nodes and build the next pointers
27 current = head
28 while current:
29 # Create a new node with the same value
30 new_node = Node(current.val)
31
32 # Link the new node to the copied list
33 tail.next = new_node
34 tail = tail.next
35
36 # Store mapping from original node to copied node
37 original_to_copy[current] = new_node
38
39 # Move to next node in original list
40 current = current.next
41
42 # Second pass: set up the random pointers using the mapping
43 current = head
44 while current:
45 # Set random pointer of copied node to the copied version of the random node
46 if current.random:
47 original_to_copy[current].random = original_to_copy[current.random]
48 else:
49 original_to_copy[current].random = None
50
51 # Move to next node in original list
52 current = current.next
53
54 # Return the head of the copied list (skip dummy node)
55 return dummy.next
56
1/*
2// Definition for a Node.
3class Node {
4 int val;
5 Node next;
6 Node random;
7
8 public Node(int val) {
9 this.val = val;
10 this.next = null;
11 this.random = null;
12 }
13}
14*/
15
16class Solution {
17 public Node copyRandomList(Node head) {
18 // HashMap to store mapping from original nodes to copied nodes
19 Map<Node, Node> oldToNewMap = new HashMap<>();
20
21 // Create a dummy node to simplify list construction
22 Node dummyHead = new Node(0);
23 Node currentTail = dummyHead;
24
25 // First pass: Create all nodes and build the next pointers
26 // Also populate the mapping from original to copied nodes
27 for (Node currentOriginal = head; currentOriginal != null; currentOriginal = currentOriginal.next) {
28 // Create a new node with the same value as the original
29 Node newNode = new Node(currentOriginal.val);
30
31 // Connect the new node to the copied list
32 currentTail.next = newNode;
33 currentTail = newNode;
34
35 // Store the mapping from original node to copied node
36 oldToNewMap.put(currentOriginal, newNode);
37 }
38
39 // Second pass: Set the random pointers for all copied nodes
40 for (Node currentOriginal = head; currentOriginal != null; currentOriginal = currentOriginal.next) {
41 // Get the corresponding copied node
42 Node copiedNode = oldToNewMap.get(currentOriginal);
43
44 // Set the random pointer of the copied node
45 // If original's random is null, set to null; otherwise, get the corresponding copied node
46 copiedNode.random = (currentOriginal.random == null) ? null : oldToNewMap.get(currentOriginal.random);
47 }
48
49 // Return the head of the copied list (skip dummy node)
50 return dummyHead.next;
51 }
52}
53
1/*
2// Definition for a Node.
3class Node {
4public:
5 int val;
6 Node* next;
7 Node* random;
8
9 Node(int _val) {
10 val = _val;
11 next = NULL;
12 random = NULL;
13 }
14};
15*/
16
17class Solution {
18public:
19 Node* copyRandomList(Node* head) {
20 // Create a dummy head for the new linked list
21 Node* dummyHead = new Node(0);
22 Node* currentNewNode = dummyHead;
23
24 // Hash map to store mapping from original nodes to their copies
25 unordered_map<Node*, Node*> originalToCopy;
26
27 // First pass: Create all nodes and establish next pointers
28 for (Node* currentOriginal = head; currentOriginal != nullptr; currentOriginal = currentOriginal->next) {
29 // Create a copy of the current node
30 Node* copiedNode = new Node(currentOriginal->val);
31
32 // Link the new node to the new list
33 currentNewNode->next = copiedNode;
34 currentNewNode = copiedNode;
35
36 // Store the mapping from original to copy
37 originalToCopy[currentOriginal] = copiedNode;
38 }
39
40 // Second pass: Set up random pointers using the mapping
41 for (Node* currentOriginal = head; currentOriginal != nullptr; currentOriginal = currentOriginal->next) {
42 // If original node has a random pointer, set the corresponding random pointer in the copy
43 if (currentOriginal->random != nullptr) {
44 originalToCopy[currentOriginal]->random = originalToCopy[currentOriginal->random];
45 } else {
46 originalToCopy[currentOriginal]->random = nullptr;
47 }
48 }
49
50 // Return the head of the copied list (skip dummy node)
51 return dummyHead->next;
52 }
53};
54
1/**
2 * Definition for _Node.
3 * class _Node {
4 * val: number
5 * next: _Node | null
6 * random: _Node | null
7 *
8 * constructor(val?: number, next?: _Node, random?: _Node) {
9 * this.val = (val===undefined ? 0 : val)
10 * this.next = (next===undefined ? null : next)
11 * this.random = (random===undefined ? null : random)
12 * }
13 * }
14 */
15
16/**
17 * Creates a deep copy of a linked list with random pointers
18 * @param head - The head node of the original linked list
19 * @returns The head node of the copied linked list
20 */
21function copyRandomList(head: _Node | null): _Node | null {
22 // Early return if the list is empty
23 if (!head) {
24 return null;
25 }
26
27 // Map to store the correspondence between original nodes and copied nodes
28 const nodeMapping: Map<_Node, _Node> = new Map();
29
30 // Create a dummy node to simplify list construction
31 const dummyHead: _Node = new _Node();
32 let currentTail: _Node = dummyHead;
33
34 // First pass: Create all nodes and build the next pointer chain
35 for (let currentNode: _Node | null = head; currentNode !== null; currentNode = currentNode.next) {
36 // Create a new node with the same value
37 const copiedNode: _Node = new _Node(currentNode.val);
38
39 // Link the new node to the copied list
40 currentTail.next = copiedNode;
41 currentTail = copiedNode;
42
43 // Store the mapping between original and copied nodes
44 nodeMapping.set(currentNode, copiedNode);
45 }
46
47 // Second pass: Set up the random pointers using the mapping
48 for (let currentNode: _Node | null = head; currentNode !== null; currentNode = currentNode.next) {
49 // Get the corresponding copied node
50 const copiedNode: _Node = nodeMapping.get(currentNode)!;
51
52 // Set the random pointer of the copied node
53 if (currentNode.random !== null) {
54 copiedNode.random = nodeMapping.get(currentNode.random)!;
55 } else {
56 copiedNode.random = null;
57 }
58 }
59
60 // Return the head of the copied list (skip dummy node)
61 return dummyHead.next;
62}
63
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two sequential while loops:
- The first loop iterates through all
n
nodes in the original linked list to create new nodes and build the mapping dictionary. Each operation inside this loop (creating a node, updating pointers, adding to dictionary) takesO(1)
time. - The second loop iterates through all
n
nodes again to set the random pointers using the dictionary lookup, which isO(1)
per operation.
Therefore, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses additional space for:
- A dictionary
d
that stores mappings from original nodes to copied nodes, containingn
key-value pairs:O(n)
- The new linked list with
n
nodes that are being created as the deep copy:O(n)
- A few constant auxiliary variables (
dummy
,tail
,cur
,node
):O(1)
The space for the output linked list is typically considered part of the space complexity for this problem, so the total space complexity is O(n)
.
Common Pitfalls
1. Attempting to Set Random Pointers Before Creating All Nodes
A common mistake is trying to set up the random pointers while creating the nodes in a single pass. This fails because a node's random pointer might point to a node that hasn't been created yet.
Incorrect Approach:
def copyRandomList(self, head: Optional['Node']) -> Optional['Node']:
if not head:
return None
original_to_copy = {}
dummy = Node(0)
tail = dummy
current = head
while current:
new_node = Node(current.val)
tail.next = new_node
tail = tail.next
original_to_copy[current] = new_node
# WRONG: Trying to set random pointer immediately
# This fails if current.random hasn't been copied yet
if current.random:
new_node.random = original_to_copy[current.random] # KeyError!
current = current.next
return dummy.next
Solution: Always use two separate passes - first create all nodes and establish the mapping, then set up the random pointers.
2. Not Handling None Values in the Hash Table Lookup
Another pitfall is forgetting that the random pointer can be None
, and trying to look up None
in the dictionary will cause a KeyError.
Incorrect Approach:
# Second pass - WRONG
current = head
while current:
# This fails when cur.random is None
original_to_copy[current].random = original_to_copy[current.random]
current = current.next
Solution: Always check if the random pointer is None
before attempting the dictionary lookup:
# Correct approach if current.random: original_to_copy[current].random = original_to_copy[current.random] else: original_to_copy[current].random = None
3. Modifying the Original List
Some might try to optimize by temporarily modifying the original list structure (like interweaving copied nodes with original nodes). This violates the requirement of keeping the original list intact.
Incorrect Approach:
# Trying to insert copied nodes between original nodes
current = head
while current:
copy = Node(current.val)
copy.next = current.next # Modifying original structure
current.next = copy
current = copy.next
Solution: Use a separate data structure (hash table) to maintain the mapping without modifying the original list.
4. Forgetting to Return the Correct Head
When using a dummy node, forgetting to return dummy.next
instead of dummy
itself is a common mistake that would include the dummy node in the result.
Solution: Always return dummy.next
to skip the dummy node and return the actual head of the copied list.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!