title: Copy List with Random Pointer slug: copy-list-with-random-pointer difficulty: medium leetcode_id: 138 leetcode_url: https://leetcode.com/problems/copy-list-with-random-pointer/ categories: - linked-lists - hash-tables patterns: - linkedlist-reversal description: | 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. constraints: | - `0 <= n <= 1000` - `-10^4 <= Node.val <= 10^4` - `Node.random` is `null` or is pointing to some node in the linked list. examples: - input: "head = [[7,null],[13,0],[11,4],[10,2],[1,0]]" output: "[[7,null],[13,0],[11,4],[10,2],[1,0]]" explanation: "A list with 5 nodes. Node 0 has value 7 and random points to null. Node 1 has value 13 and random points to node 0. Node 2 has value 11 and random points to node 4. And so on. The output is a deep copy with the same structure." - input: "head = [[1,1],[2,1]]" output: "[[1,1],[2,1]]" explanation: "Two nodes where both random pointers point to node 1 (the second node with value 2)." - input: "head = [[3,null],[3,0],[3,null]]" output: "[[3,null],[3,0],[3,null]]" explanation: "Three nodes all with value 3. The middle node's random pointer points to the first node." explanation: intuition: | Imagine you're tasked with duplicating a spider web where each thread (the `next` pointer) connects to the next junction, but there are also random silk threads (`random` pointers) that can connect any junction to any other junction — or nowhere at all. The challenge is that when you're building your copy, you need to connect the `random` pointer of a copied node to another *copied* node. But how do you find the copied version of the node that the original `random` points to? Think of it like this: if you're copying a contact list where each person has a "best friend" field pointing to another person in the same list, you can't fill in the "best friend" until you've created entries for *everyone*. The core insight is that you need a way to **map original nodes to their copies**. There are two elegant approaches: 1. **Hash Map**: Use a dictionary to store the mapping from each original node to its copy. This makes lookups instant but uses O(n) extra space. 2. **Interweaving**: Cleverly weave copied nodes directly into the original list (A → A' → B → B' → ...), so each copy sits right next to its original. The original's `random.next` gives you the copy's random target. Then unweave to separate the lists. approach: | We'll describe the **Hash Map Approach** as the primary solution due to its clarity: **Step 1: Handle the edge case** - If `head` is `null`, return `null` immediately   **Step 2: First pass — create all copied nodes** - Traverse the original list - For each original node, create a new node with the same value - Store the mapping `original_node → copied_node` in a hash map - This ensures we have all copies created before wiring up pointers   **Step 3: Second pass — wire up `next` and `random` pointers** - Traverse the original list again - For each original node: - Set `copy.next = hash_map[original.next]` (or `null` if `original.next` is `null`) - Set `copy.random = hash_map[original.random]` (or `null` if `original.random` is `null`) - The hash map allows O(1) lookup of any copied node   **Step 4: Return the copied head** - Return `hash_map[head]` — the copy of the original head node common_pitfalls: - title: Copying Random Pointers to Original Nodes description: | A common mistake is to set `copy.random = original.random`. This makes the copy's random pointer point to a node in the *original* list, not the copied list. The problem explicitly states: "None of the pointers in the new list should point to nodes in the original list." You must translate every pointer to point to the corresponding *copy*. wrong_approach: "copy.random = original.random" correct_approach: "copy.random = hash_map[original.random]" - title: Single Pass Without Pre-Creating Nodes description: | You might try to copy nodes and wire pointers in a single pass. But consider: when processing node A, its `random` might point to node Z which you haven't created yet. Either use two passes (create all nodes first, then wire pointers), or create nodes on-demand and check the hash map before creating duplicates. wrong_approach: "Single pass assuming nodes exist" correct_approach: "Two-pass or create-on-demand with hash map" - title: Not Handling Null Pointers description: | Both `next` and `random` can be `null`. When looking up in the hash map, ensure you handle the case where the key is `null`. In Python, `hash_map.get(None)` returns `None`, which is correct. In other languages, you may need explicit null checks. wrong_approach: "Unconditionally accessing hash_map[node]" correct_approach: "Check if node is null before hash map lookup" key_takeaways: - "**Hash map for node mapping**: When cloning graph-like structures, a hash map from original to copy nodes enables O(1) pointer translation" - "**Two-pass pattern**: Create all nodes first, then wire connections — this avoids forward reference problems" - "**Space-time tradeoff**: The interweaving approach achieves O(1) space but is trickier to implement; hash map is clearer at O(n) space" - "**Deep copy fundamentals**: This problem teaches the core concept of deep copying interconnected structures — applicable to graphs, trees with parent pointers, and more" time_complexity: "O(n). We traverse the list twice — once to create copies, once to wire pointers. Each node is visited a constant number of times." space_complexity: "O(n). The hash map stores a mapping for each of the `n` nodes. The output list itself also uses O(n) space, but that's required by the problem." solutions: - approach_name: Hash Map is_optimal: true code: | class Node: def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None): self.val = x self.next = next self.random = random def copyRandomList(head: 'Node') -> 'Node': if not head: return None # Map from original node to its copy old_to_new = {} # First pass: create all copied nodes current = head while current: old_to_new[current] = Node(current.val) current = current.next # Second pass: wire up next and random pointers current = head while current: copy = old_to_new[current] # Wire next pointer (use .get() to handle None gracefully) copy.next = old_to_new.get(current.next) # Wire random pointer copy.random = old_to_new.get(current.random) current = current.next # Return the copy of the head return old_to_new[head] explanation: | **Time Complexity:** O(n) — Two linear passes through the list. **Space Complexity:** O(n) — Hash map stores n mappings. The hash map approach is intuitive: first create all copies, then use the map to translate every pointer from the original domain to the copy domain. The `.get()` method handles `None` keys gracefully by returning `None`. - approach_name: Interweaving Nodes is_optimal: false code: | def copyRandomList(head: 'Node') -> 'Node': if not head: return None # Step 1: Interweave copied nodes into the original list # Original: A -> B -> C # After: A -> A' -> B -> B' -> C -> C' current = head while current: copy = Node(current.val) copy.next = current.next current.next = copy current = copy.next # Step 2: Wire up random pointers for copied nodes current = head while current: copy = current.next if current.random: # The copy of current.random is current.random.next copy.random = current.random.next current = copy.next # Step 3: Unweave the lists to separate original and copy current = head copy_head = head.next while current: copy = current.next # Restore original list current.next = copy.next # Wire copy list if copy.next: copy.next = copy.next.next current = current.next return copy_head explanation: | **Time Complexity:** O(n) — Three linear passes through the list. **Space Complexity:** O(1) — No hash map; copies are woven into the original list temporarily. This clever approach avoids extra space by placing each copy immediately after its original. The trick is that `original.random.next` gives us the copy of the random target. After wiring random pointers, we unweave to restore the original list and extract the copy. While space-efficient, it's more complex and modifies the original list temporarily.