Clone Linked List with Random Pointer in DSA Rust - Time & Space Complexity
We want to understand how long it takes to copy a linked list where each node has a random pointer.
How does the time needed grow as the list gets bigger?
Analyze the time complexity of the following code snippet.
struct Node {
val: i32,
next: Option>,
random: Option<*mut Node>,
}
fn clone_list(head: &Option>) -> Option> {
// Step 1: Insert cloned nodes
// Step 2: Assign random pointers
// Step 3: Separate cloned list
None // placeholder
}
This code clones a linked list where each node has a next and a random pointer.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Three passes over the linked list nodes.
- How many times: Each pass visits every node once.
Each node is visited three times, so the total work grows directly with the number of nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | ~30 (3 x 10) |
| 100 | ~300 (3 x 100) |
| 1000 | ~3000 (3 x 1000) |
Pattern observation: The operations increase linearly as the list size grows.
Time Complexity: O(n)
This means the time to clone the list grows in a straight line with the number of nodes.
[X] Wrong: "Cloning the list takes more than linear time because of the random pointers."
[OK] Correct: Each node's random pointer is handled in the same pass without extra nested loops, so it does not multiply the work.
Understanding this linear time cloning method shows you can handle complex pointer structures efficiently, a useful skill in many coding challenges.
"What if we used a hash map to store cloned nodes instead of modifying the original list? How would the time complexity change?"
Practice
Solution
Step 1: Understand the structure of the linked list
The linked list has two pointers: next and random. The next pointer points to the next node, and the random pointer can point to any node or null.Step 2: Identify the cloning challenge
While copying nodes is straightforward, correctly assigning the random pointers in the cloned list is tricky because they can point anywhere in the original list.Final Answer:
Copying the random pointers correctly to the new nodes -> Option BQuick Check:
Random pointer cloning = main challenge [OK]
- Ignoring random pointers during cloning
- Assuming random pointers follow next pointers
- Copying only data without pointers
Solution
Step 1: Understand Rust pointer types for linked lists
Rust uses Option for owning next nodes. For random pointers, raw pointers (*mut Node) are used to avoid ownership conflicts.Step 2: Analyze each struct definition
struct Node { val: i32, next: Option, random: Option<*mut Node> } uses Option for next and Option<*mut Node> for random, which is correct. struct Node { val: i32, next: Option, random: Option } wrongly uses Box<Node> for random, causing ownership issues. struct Node { val: i32, next: Option<Node>, random: Option<Node> } uses Node directly, which is invalid. struct Node { val: i32, next: Box<Node>, random: Box<Node> } uses Box<Node> without Option, disallowing null.Final Answer:
struct Node { val: i32, next: Option, random: Option<*mut Node> } -> Option AQuick Check:
Correct pointer types = struct Node { val: i32, next: Option, random: Option<*mut Node> } [OK]
- Using Box<Node> without Option for nullable pointers
- Using Box<Node> for random causing ownership errors
- Using Node directly instead of Box or pointer
fn print_list(head: &Option) {
let mut current = head;
while let Some(node) = current {
let random_val = match node.random {
Some(ptr) => unsafe { (*ptr).val },
None => -1,
};
println!("{} -> Random: {}", node.val, random_val);
current = &node.next;
}
}
// Original list: 1 -> 2 -> null
// Random pointers: 1.random -> 2, 2.random -> 1
// After cloning, print cloned list using print_list(cloned_head)
Solution
Step 1: Understand the print_list function
The function prints each node's value and the value of the node pointed by its random pointer. If random is None, it prints -1.Step 2: Analyze the original and cloned list random pointers
Original list has 1.random -> 2 and 2.random -> 1. The cloned list should preserve these random pointer relations, so printing cloned list shows the same pattern.Final Answer:
1 -> Random: 2 2 -> Random: 1 -> Option CQuick Check:
Random pointers preserved = output B [OK]
- Assuming random pointers reset to self or None
- Confusing node values with random pointer values
- Thinking unsafe block causes compile error
fn clone_list(head: &Option) -> Option {
let mut map = std::collections::HashMap::new();
let mut current = head;
while let Some(node) = current {
let new_node = Box::new(Node { val: node.val, next: None, random: None });
map.insert(node as *const _, new_node);
current = &node.next;
}
// Second pass to assign next and random pointers
// ...
None
}Solution
Step 1: Analyze map insertion of new_node
Inserting new_node (Box<Node>) into map moves ownership of the cloned node into the HashMap, preventing proper linking of next and random pointers.Step 2: Understand ownership rules in Rust cloning
To keep original nodes intact, cloned nodes should be stored separately without moving original nodes. The map should store references or pointers, not move ownership.Final Answer:
Inserting Box<Node> into map moves ownership of cloned nodes, preventing proper linking -> Option DQuick Check:
Ownership moved incorrectly = bug C [OK]
- Assuming raw pointers cause runtime errors
- Ignoring ownership move semantics
- Forgetting to clone node values
Solution
Step 1: Understand the interleaving technique
Insert cloned nodes immediately after original nodes, so each original node points to its clone. This allows setting random pointers by referencing original nodes' random pointers' next nodes.Step 2: Separate the interleaved list into original and cloned lists
After setting random pointers, detach cloned nodes to form the cloned list, restoring the original list.Final Answer:
Interleave cloned nodes between original nodes, then separate lists -> Option AQuick Check:
Interleaving nodes = no extra space cloning [OK]
- Using recursion causes stack overflow on large lists
- Using vector wastes extra space
- Stack approach is not standard for this problem
Ready to prove it?
Pass the test and this pattern moves unknown
