95. Copy List with Random Pointer
π LeetCode Problem: 138. Copy List with Random Pointer
π Difficulty: Medium
π·οΈ Topics: Hash Table, Linked List
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.
For example, if there are two nodes X and Y in the original list, where X.random --> Y, then for the corresponding two nodes x and y in the copied list, x.random --> y.
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 from 0 to n-1) that the random pointer points to, or null if it does not point to any node.
Your code will only be given the head of the original 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]]
Visual:
Original:
7 β 13 β 11 β 10 β 1
β β β β β
null 7 1 11 7
Deep copy (new nodes, same structure):
7' β 13' β 11' β 10' β 1'
β β β β β
null 7' 1' 11' 7'
Example 2:
Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]
Visual:
Original:
1 β 2
β β
2 2
Copy:
1' β 2'
β β
2' 2'
Example 3:
Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]
Constraints:
- 0 <= n <= 1000
- -10^4 <= Node.val <= 10^4
- Node.random is null or is pointing to some node in the linked list.
π ELI5: The Simple Idea!
Think of photocopying a document with footnotes:
Original document:
Page 1 β Page 2 β Page 3
Page 1 has footnote pointing to Page 3
Page 2 has footnote pointing to Page 1
Page 3 has footnote pointing to Page 2
When photocopying:
β WRONG: Copy pages but footnotes still point to ORIGINAL pages
β CORRECT: Copy pages AND update footnotes to point to COPIED pages
Same with linked list:
Must copy nodes
AND update random pointers to point to copied nodes
NOT original nodes!
The Challenge:
When copying node X:
X.next = Y (easy to copy)
X.random = Z (how to find copied Z?)
Need mapping: Original β Copy
π¨ Visual Understanding
The Deep Copy Problem
Original List:
1 β 2 β 3 β null
β β β
3 1 2
Goal: Create NEW list with SAME structure
Wrong approach (shallow copy):
1' β 2' β 3' β null
β β β
3 1 2 β Points to ORIGINAL nodes! β
Correct approach (deep copy):
1' β 2' β 3' β null
β β β
3' 1' 2' β Points to COPIED nodes! β
Why We Need a Map
Original:
Node A (val=7) β Node B (val=13) β Node C (val=11)
A.random = C
B.random = A
C.random = B
Creating copies:
Step 1: Create A' (val=7)
Step 2: Create B' (val=13)
Step 3: Create C' (val=11)
Step 4: Set random pointers
A'.random should point to C'
But how to find C' given C?
Solution: Map!
map[A] = A'
map[B] = B'
map[C] = C'
Now: A'.random = map[C] = C' β
π― Approach 1: HashMap (Two Pass) β
The Standard Solution
Algorithm:
Pass 1: Create all new nodes, build map
- For each original node, create copy
- Store: map[original] = copy
Pass 2: Set next and random pointers
- For each original node:
copy.next = map[original.next]
copy.random = map[original.random]
Implementation
/**
* Two-pass with HashMap
* Time: O(n), Space: O(n)
*/
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// Map: original node -> copied node
Map<Node, Node> map = new HashMap<>();
// Pass 1: Create all nodes and build map
Node curr = head;
while (curr != null) {
map.put(curr, new Node(curr.val));
curr = curr.next;
}
// Pass 2: Set next and random pointers
curr = head;
while (curr != null) {
Node copy = map.get(curr);
copy.next = map.get(curr.next); // Can be null
copy.random = map.get(curr.random); // Can be null
curr = curr.next;
}
return map.get(head);
}
β° Time: O(n) - Two passes through the list
πΎ Space: O(n) - HashMap stores n nodes
π Super Detailed Dry Run
Example: [[7,null],[13,0],[11,4],[10,2],[1,0]]
Original List:
Node A (val=7, random=null)
Node B (val=13, random=A)
Node C (val=11, random=E)
Node D (val=10, random=C)
Node E (val=1, random=A)
Structure:
A β B β C β D β E β null
β β β β β
null A E C A
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PASS 1: Create Nodes and Build Map
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
curr = A (val=7)
Iteration 1:
Create: A' = new Node(7)
map[A] = A'
curr = B
Map state:
A β A'
Iteration 2:
Create: B' = new Node(13)
map[B] = B'
curr = C
Map state:
A β A'
B β B'
Iteration 3:
Create: C' = new Node(11)
map[C] = C'
curr = D
Map state:
A β A'
B β B'
C β C'
Iteration 4:
Create: D' = new Node(10)
map[D] = D'
curr = E
Map state:
A β A'
B β B'
C β C'
D β D'
Iteration 5:
Create: E' = new Node(1)
map[E] = E'
curr = null
Map state:
A β A'
B β B'
C β C'
D β D'
E β E'
After Pass 1:
All copied nodes created (no connections yet)
A', B', C', D', E' are isolated nodes
Map fully built
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PASS 2: Set Next and Random Pointers
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
curr = A
Iteration 1: Process node A
copy = map[A] = A'
Set next:
A.next = B
copy.next = map[B] = B'
A'.next = B' β
Set random:
A.random = null
copy.random = map[null] = null
A'.random = null β
curr = B
Current state of A':
A' β B' (next set)
β
null (random set)
Iteration 2: Process node B
copy = map[B] = B'
Set next:
B.next = C
copy.next = map[C] = C'
B'.next = C' β
Set random:
B.random = A
copy.random = map[A] = A'
B'.random = A' β
curr = C
Current state:
A' β B' β C'
β β
null A'
Iteration 3: Process node C
copy = map[C] = C'
Set next:
C.next = D
copy.next = map[D] = D'
C'.next = D' β
Set random:
C.random = E
copy.random = map[E] = E'
C'.random = E' β
curr = D
Current state:
A' β B' β C' β D'
β β β
null A' E'
Iteration 4: Process node D
copy = map[D] = D'
Set next:
D.next = E
copy.next = map[E] = E'
D'.next = E' β
Set random:
D.random = C
copy.random = map[C] = C'
D'.random = C' β
curr = E
Current state:
A' β B' β C' β D' β E'
β β β β
null A' E' C'
Iteration 5: Process node E
copy = map[E] = E'
Set next:
E.next = null
copy.next = map[null] = null
E'.next = null β
Set random:
E.random = A
copy.random = map[A] = A'
E'.random = A' β
curr = null
Final state:
A' β B' β C' β D' β E' β null
β β β β β
null A' E' C' A'
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Return Result
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Return: map[head] = map[A] = A'
Deep copy complete! β
All pointers point to copied nodes, not originals
π― Approach 2: Interleaving (O(1) Space) ββ
The Space-Optimized Solution
Algorithm:
Step 1: Interleave original and copy nodes
A β B β C becomes A β A' β B β B' β C β C'
Step 2: Set random pointers
A'.random = A.random.next (the copy after original's random)
Step 3: Restore original list and extract copy
Split: A β A' β B β B' into A β B and A' β B'
Implementation
/**
* Interleaving approach (O(1) space)
* Time: O(n), Space: O(1)
*/
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// Step 1: Create copy nodes and interleave
Node curr = head;
while (curr != null) {
Node copy = new Node(curr.val);
copy.next = curr.next;
curr.next = copy;
curr = copy.next;
}
// Step 2: Set random pointers for copied nodes
curr = head;
while (curr != null) {
if (curr.random != null) {
curr.next.random = curr.random.next;
}
curr = curr.next.next;
}
// Step 3: Separate lists
Node dummy = new Node(0);
Node copyCurr = dummy;
curr = head;
while (curr != null) {
Node copy = curr.next;
// Restore original list
curr.next = copy.next;
// Build copy list
copyCurr.next = copy;
copyCurr = copy;
curr = curr.next;
}
return dummy.next;
}
β° Time: O(n) - Three passes
πΎ Space: O(1) - No extra data structures (excluding output)
π Detailed Dry Run (Interleaving)
Example: [7,null] β [13,0] β [1,0]
Original:
A(7) β B(13) β C(1) β null
β β β
null A A
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
STEP 1: Interleave Copies
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Process A:
Create A'(7)
A'.next = B
A.next = A'
A β A' β B β C β null
Process B:
Create B'(13)
B'.next = C
B.next = B'
A β A' β B β B' β C β null
Process C:
Create C'(1)
C'.next = null
C.next = C'
A β A' β B β B' β C β C' β null
After Step 1 (interleaved):
A β A' β B β B' β C β C' β null
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
STEP 2: Set Random Pointers
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Process A:
A.random = null
Skip (no random)
Process B:
B.random = A
B'.random = A.next = A' β
B' β A' (random)
Process C:
C.random = A
C'.random = A.next = A' β
C' β A' (random)
After Step 2:
Structure:
A β A' β B β B' β C β C' β null
β β
A' A'
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
STEP 3: Separate Lists
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Process A:
copy = A' (A.next)
Restore original:
A.next = A'.next = B
Build copy:
copyCurr.next = A'
copyCurr = A'
Process B:
copy = B'
Restore:
B.next = B'.next = C
Build copy:
A'.next = B'
copyCurr = B'
Process C:
copy = C'
Restore:
C.next = C'.next = null
Build copy:
B'.next = C'
copyCurr = C'
After Step 3:
Original: A β B β C β null
Copy: A' β B' β C' β null
β β β
null A' A'
Both lists restored correctly! β
π― Approach 3: Recursive with HashMap
Recursive Solution
/**
* Recursive approach with memoization
* Time: O(n), Space: O(n)
*/
private Map<Node, Node> map = new HashMap<>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// If already copied, return cached copy
if (map.containsKey(head)) {
return map.get(head);
}
// Create copy
Node copy = new Node(head.val);
map.put(head, copy);
// Recursively copy next and random
copy.next = copyRandomList(head.next);
copy.random = copyRandomList(head.random);
return copy;
}
β° Time: O(n)
πΎ Space: O(n) - HashMap + recursion stack
π― Comparison of Approaches
Approach Time Space Complexity Recommended
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
HashMap 2-Pass O(n) O(n) Simple βββββ
Interleaving O(n) O(1) Medium ββββ
Recursive O(n) O(n) Medium βββ
Best: HashMap (for interviews)
β Clearest logic
β Easy to explain
β Two clean passes
β Most common in interviews
Advanced: Interleaving
β Optimal space O(1)
β Shows deep understanding
β Great follow-up answer
β More complex to implement
β οΈ Common Mistakes
Mistake 1: Pointing to original nodes
// β WRONG - Random points to original!
copy.random = curr.random; // Points to ORIGINAL node!
// β CORRECT - Use map
copy.random = map.get(curr.random); // Points to COPY!
Mistake 2: Not handling null pointers
// β WRONG - NullPointerException!
copy.next = map.get(curr.next); // What if curr.next is null?
// β CORRECT - map.get(null) returns null
copy.next = map.get(curr.next); // Works! null stays null
Mistake 3: Single pass attempt (won't work)
// β WRONG - Can't set random if target not created yet!
while (curr != null) {
Node copy = new Node(curr.val);
copy.random = /* ... target might not exist yet! */
}
// β CORRECT - Need two passes
// Pass 1: Create all nodes
// Pass 2: Set pointers (all nodes exist now)
Mistake 4: Interleaving - wrong random assignment
// β WRONG
curr.next.random = curr.random; // Points to original!
// β CORRECT
curr.next.random = curr.random.next; // Points to copy!
Mistake 5: Not restoring original list (interleaving)
// β WRONG - Original list modified!
// Just extract copies, don't restore original
// β CORRECT - Restore original
curr.next = copy.next; // Restore original's next pointer
π― Pattern Recognition
Problem Type: Deep Copy with Extra Pointers
Core Pattern: HashMap Mapping (Original β Copy)
When to Apply:
β Deep copy linked list
β Clone graph/tree
β Multiple pointer types
β Need mapping between original and copy
Recognition Keywords:
- "deep copy"
- "clone"
- "random pointer"
- "additional pointer"
- "copy linked list"
Similar Problems:
- Clone Graph (LC 133) - Same pattern
- Clone N-ary Tree (LC 1490) - Tree version
- Copy List with Arbitrary Pointer - Variation
Key Components:
ββββββββββββββββββββββββββββββββββββββββββββββ
β HashMap: map[original] = copy β
β Pass 1: Create all nodes β
β Pass 2: Set all pointers β
β Handle Null: map.get(null) = null β
β Time: O(n), Space: O(n) or O(1) β
ββββββββββββββββββββββββββββββββββββββββββββββ
π§ Interview Strategy
Step 1: "I need to create a deep copy with random pointers"
Step 2: "I'll use a HashMap to map original nodes to copies"
Step 3: "Two passes: create nodes, then set pointers"
Key Points to Mention:
- Deep copy means new nodes, not shallow references
- Need mapping to find copied nodes
- Two-pass approach: create all, then connect all
- HashMap: original β copy
- map.get(null) returns null (handles null pointers)
- Alternative: O(1) space with interleaving
Walk Through Example:
"For nodes AβBβC with random pointers:
Pass 1: Create A', B', C' and build map
Pass 2: For each original node, set copy's pointers
A'.next = map[A.next] = B'
A'.random = map[A.random] = mapped copy
Result: Fully copied list with correct structure"
Follow-up Answer (if asked about O(1) space):
"I can use interleaving approach:
1. Insert copies: AβA'βBβB'βCβC'
2. Set randoms: A'.random = A.random.next
3. Split lists: separate original and copy
This uses O(1) extra space but is more complex"
Edge Cases to Mention:
β Empty list β return null
β Single node β copy with same random
β Random points to null β copy has null random
β Random points to self β copy points to self
β Circular random pointers β handled by map
π Quick Revision Notes
π― Core Concept:
Copy List with Random Pointer: Use HashMap to map originalβcopy. Pass 1: Create all copied nodes, build map. Pass 2: Set pointers using map: copy.next = map[orig.next], copy.random = map[orig.random]. Critical: map.get(null) returns null (handles null pointers). Alternative: Interleaving for O(1) space.
β‘ Quick Implementation (HashMap):
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Node {
int val;
Node next;
Node random;
Node(int val) { this.val = val; this.next = null; this.random = null; }
}
class Solution {
public static Node create(List<Integer> list) {
Node dummy = new Node(-1);
Node curr = dummy;
for(int num : list) {
curr.next = new Node(num);
curr = curr.next;
}
return dummy.next;
}
public static void print(Node head) {
Node curr = head;
while (curr != null) {
System.out.print(curr.val + " -> ");
curr = curr.next;
}
System.out.println();
}
public Node copyRandomList(Node head) {
if(head == null) {
return head;
}
// Approach 1 - using hashmap
// Create map where old node is key and new node is value.
// (A, A1) -> (B, B1) -> (C, C1) -> (D, D1) and so on simply for now.
// Why we do like this? because we do not have access to future nodes.
// Concept is every node in original list is stored in a map whose value is
// node in the newly created list. Hence in the second pass, when we access
// the random pointer, we get random pointer of the newly created list.
HashMap<Node, Node> map = new HashMap<>();
Node curr = head;
Node dummy = new Node(-1);
Node currDash = dummy;
while (curr != null) {
currDash.next = new Node(curr.val);
currDash = currDash.next;
map.put(curr, currDash);
curr = curr.next;
}
// Again pass from start now for random pointers.
// If node 2 is like (C, A), map stores (A, A'). So, (C`, map.get(A)) which is (C`, A`)
curr = head;
currDash = dummy.next;
while (curr != null) {
currDash.random = map.get(curr.random);
curr = curr.next;
currDash = currDash.next;
}
return dummy.next;
// Approach 2 - space interleaving - left as it is little complex and also not interview recommended
}
public static void main(String[] args) {
Solution t = new Solution();
Node list1 = create(Arrays.asList(1,2,3,4,5));
print(t.copyRandomList(list1));
}
}
π Key Insights:
- Pattern: HashMap Mapping
- Two passes: Create all, then connect all
- Map: original β copy (enables lookup)
- Null safe: map.get(null) = null
- Deep copy: New nodes, not references
- Time: O(n), Space: O(n) or O(1) β
πͺ Memory Aid:
"Create all clones, then connect using map!"
"Clone β Map β Connect!" β¨
β οΈ Critical Rule:
NEVER point to original nodes!
β WRONG:
copy.random = orig.random // Points to original!
β CORRECT:
copy.random = map[orig.random] // Points to copy!
The map is the KEY to finding copied nodes!
π§ͺ Edge Cases
Case 1: Empty list
Input: head = null
Output: null
Handled: Early return
Case 2: Single node, random to self
Input: [[1,0]]
Node points to itself
Output: [[1,0]]
Copy also points to itself
Case 3: All random pointers null
Input: [[1,null],[2,null],[3,null]]
Output: [[1,null],[2,null],[3,null]]
Handled: map.get(null) = null
Case 4: Random forms cycle
A.random = B, B.random = A
Handled: Map prevents issues
All handled perfectly! β
Related Problems
- Clone Graph (LC 133) - Same HashMap pattern
- Clone Binary Tree with Random Pointer (LC 1485) - Tree version
- Clone N-ary Tree (LC 1490) - N-ary tree
Happy practicing! π―
Note: This problem teaches the fundamental deep copy pattern using HashMap! This exact pattern appears in graph cloning, tree cloning, and many other problems. The two-pass approach (create all, then connect all) is the key insight. Master this and you'll handle any cloning problem! πͺβ¨