Copy Linked List with Random Pointer is a classic interview problem that tests a candidate’s ability to manipulate data structures while preserving complex relationships. The task is to create a deep copy of a singly‑linked list where each node contains, besides the usual next pointer, an additional random pointer that may point to any node in the list—or to null. The challenge lies in reproducing both the next and random links without altering the original list and without using extra space proportional to the number of nodes (unless you deliberately choose a method that does). Below we explore the problem in depth, walk through two popular solutions, analyze their complexities, and provide implementation details in multiple languages.
Understanding the Problem
A node in the original list can be defined as:
class Node {
int val;
Node next;
Node random;
}
valholds the node’s data.nextpoints to the subsequent node in the list (ornullfor the tail).randompoints to any node in the list (including itself) ornull.
The goal is to return the head of a new list where each new node mirrors the val of its counterpart and whose next and random pointers replicate the original structure exactly. Importantly, the original list must remain unchanged after the operation.
Why It’s Tricky
If we merely iterate and create new nodes while setting next pointers, we can easily rebuild the linear chain. The difficulty appears when we try to assign random pointers because the target node may not have been created yet, or we may need to locate it efficiently without scanning the entire list each time (which would lead to O(n²) time) That's the part that actually makes a difference..
Two main strategies solve this elegantly:
- Hash‑Map (Dictionary) Approach – O(n) time, O(n) extra space.
- Interleaving (Weaving) Approach – O(n) time, O(1) extra space (modifies the list temporarily).
Both guarantee a correct deep copy; the choice depends on whether you are allowed to alter the original list temporarily.
Solution 1: Hash‑Map Approach
Idea
While traversing the original list once, we create a copy of each node and store a mapping from the original node to its copy in a hash map. In a second pass, we use this map to set the next and random pointers of each copied node by looking up the corresponding copies of the original nodes’ next and random.
Step‑by‑Step
-
First Pass – Node Creation
- Initialize an empty hash map
oldToNew. - Iterate through the original list with a pointer
curr. - For each
curr, create a new nodecopy = new Node(curr.val). - Store the mapping
oldToNew[curr] = copy. - Advance
curr = curr.next.
- Initialize an empty hash map
-
Second Pass – Pointer Assignment
- Iterate again through the original list.
- For each
curr, retrieve its copycopy = oldToNew[curr]. - Set
copy.next = oldToNew.get(curr.next)(handlesnullautomatically). - Set
copy.random = oldToNew.get(curr.random). - Move to the next original node.
-
Return the head of the copied list, which is
oldToNew.get(originalHead)(ornullif the input was empty).
Pseudocode
function copyRandomList(head):
if head == null: return null
map = new HashMap()
curr = head
while curr != null:
map[curr] = new Node(curr.val)
curr = curr.next
curr = head
while curr != null:
copy = map[curr]
copy.next = map.get(curr.next)
copy.random = map.get(curr.random)
curr = curr.next
return map[head]
Complexity
| Aspect | Detail |
|---|---|
| Time Complexity | O(n) – two linear passes |
| Space Complexity | O(n) – hash map stores n entries |
| Modifies Input | No (original list stays intact) |
Implementation Snippets
Python
class Node:
def __init__(self, val, next=None, random=None):
self.val = val
self.next = next
self.random = random
def copyRandomList(head):
if not head:
return None
old_to_new = {}
curr = head
while curr:
old_to_new[curr] = Node(curr.val)
curr = curr.Worth adding: next
curr = head
while curr:
copy = old_to_new[curr]
copy. next = old_to_new.Day to day, get(curr. On the flip side, next)
copy. random = old_to_new.And get(curr. random)
curr = curr.
**Java**
```java
class Node {
int val;
Node next;
Node random;
Node(int val) { this.val = val; }
}
public Node copyRandomList(Node head) {
if (head == null) return null;
Map map = new HashMap<>();
Node curr = head;
while (curr != null) {
map.Because of that, put(curr, new Node(curr. val));
curr = curr.next;
}
curr = head;
while (curr != null) {
Node copy = map.On top of that, get(curr);
copy. next = map.get(curr.Consider this: next);
copy. Day to day, random = map. On top of that, get(curr. Day to day, random);
curr = curr. next;
}
return map.
**C++**
```cpp
struct Node {
int val;
Node* next;
Node* random;
Node(int x) : val(x), next(nullptr), random(nullptr) {}
};
Node* copyRandomList(Node* head) {
if (!head) return nullptr;
unordered_map map;
Node* curr = head;
while (curr) {
map[curr] = new Node(curr->val);
curr = curr->next;
}
curr = head;
while (curr) {
Node* copy = map[curr];
copy->next = map[curr->next];
copy->random = map[curr->random];
curr = curr->next;
}
return map[head];
}
Not the most exciting part, but easily the most useful.
Solution 2: Interleaving (Weaving) Approach – O(1) Extra Space
Idea
Instead of a hash map, we interleave copied nodes directly after each original node. The list temporarily looks like:
Original1 -> Copy1 -> Original2 -> Copy2 -> Original3 -> Copy3 -> …
Because each copy sits immediately after its original, we can set the random pointer of a copy by accessing original.next (which points to the copy of the original’s random target). This leads to random. After establishing all pointers, we detach the interleaved list to restore the original and extract the copied list.
Step‑by‑Step
- Insert Copies
- Traverse the original list. For each node `orig