questions C
This commit is contained in:
210
backend/data/questions/copy-list-with-random-pointer.yaml
Normal file
210
backend/data/questions/copy-list-with-random-pointer.yaml
Normal file
@@ -0,0 +1,210 @@
|
||||
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.
|
||||
Reference in New Issue
Block a user