Problem 138 Copy List with Random Pointer
Table of Contents
Problem Statement
Link - Problem 138
Question
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 representingNode.val
random_index
: the index of the node (range from0
ton-1
) that therandom
pointer points to, ornull
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]]
Example 2:
Input: head = [[1,1],[2,1]] Output: [[1,1],[2,1]]
Example 3:
Input: head = [[3,null],[3,0],[3,null]] Output: [[3,null],[3,0],[3,null]]
Constraints:
0 <= n <= 1000
-104 <= Node.val <= 104
Node.random
isnull
or is pointing to some node in the linked list.
Solution
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
std::ios::sync_with_stdio(false);
if (!head) return nullptr;
Node* curr = head;
while (curr) {
Node* new_node = new Node(curr->val);
new_node->next = curr->next;
curr->next = new_node;
curr = new_node->next;
}
curr = head;
while (curr) {
if (curr->random) {
curr->next->random = curr->random->next;
}
curr = curr->next->next;
}
Node* old_head = head;
Node* new_head = head->next;
Node* curr_old = old_head;
Node* curr_new = new_head;
while (curr_old) {
curr_old->next = curr_old->next->next;
curr_new->next = curr_new->next ? curr_new->next->next : nullptr;
curr_old = curr_old->next;
curr_new = curr_new->next;
}
return new_head;
}
};
Complexity Analysis
| Algorithm | Time Complexity | Space Complexity |
| ---------------------------------------- | --------------- | ---------------- |
| Two-pointer traversal with Graph cloning | O(n) | O(n) |
Explanation
Intial Thoughts
When approaching this problem, it’s essential to consider the interdependencies between the nodes due to the random pointers. One approach is to create a simple hash map that maps the original nodes to their deep copies. However, this approach has a time complexity of O(n) and space complexity of O(n) as well. Alternatively, we can think of a strategy to create a copy of each node in the original list, altering the next pointers to accommodate new nodes while preserving the original connections. We can differentiate the cloned nodes by setting the next pointer of the original node to the cloned node, a strategy referred to as ‘weaving the clone list with the original list’. We then fix the random pointers of the cloned nodes by following the original connections. This approach only involves changing the original list temporarily and finally decouples the cloned list from the original list, ensuring that no references from the cloned list to the original nodes are left. The time complexity for this approach remains O(n) and the space complexity is also O(1), not including the output space for the cloned list. Considering we are dealing with a deeply complex data structure like linked lists and want to optimize the solution for both space and time, the two-pass approach strategy seems the most fitting. We can apply this strategy to break down the original problem and finally restore the cloned version of the linked list.
Intuitive Analysis
To develop the problem-solving intuition, think of linked lists as forming an intricate dance. First, we fix the ’next step’ for each pair of nodes. Going forward, notice how original pointers were pointing not to their own similar pairs but individual nodes instead, then replicate that in the next step for the deep copied nodes. Lastly, understand that even restoring original connections by going back and fixing them, a new, independent network has been created that mirrors the original but exists independently in one whole piece.
1. Intuition
- The problem requires creating a deep copy of a linked list with random pointers.
- A naive approach would be to create a new node for each node in the original list and then update the next and random pointers.
- However, this approach would not work because the random pointers in the new list would point to nodes in the original list, not the new list.
- To solve this problem, we need to create a new node for each node in the original list and then update the next and random pointers in two separate passes.
- The first pass creates new nodes and updates the next pointers, while the second pass updates the random pointers.
- This approach ensures that the random pointers in the new list point to nodes in the new list, not the original list.
- The time complexity of this approach is O(n), where n is the number of nodes in the original list.
2. Implementation
- We start by checking if the head of the original list is null, and if so, we return null.
- We then create a new node for each node in the original list and update the next pointers using a while loop:
while (curr) { Node* new_node = new Node(curr->val); new_node->next = curr->next; curr->next = new_node; curr = new_node->next; }
- Next, we update the random pointers using another while loop:
while (curr) { if (curr->random) { curr->next->random = curr->random->next; } curr = curr->next->next; }
- We then separate the new list from the original list using two pointers:
Node* old_head = head; Node* new_head = head->next; Node* curr_old = old_head; Node* curr_new = new_head;
- Finally, we return the head of the new list:
return new_head;
- The
std::ios::sync_with_stdio(false);
line is used to improve performance by disabling synchronization of C++ streams with C streams. - The
if (!head) return nullptr;
line is used to handle the edge case where the input list is empty.
Complexity Analysis
Time Complexity:
- The solution iterates through the linked list three times: once to create new nodes and insert them into the original list, once to adjust the random pointers of the new nodes, and once to split the list into the original and the cloned lists.
- The dominant operations in each iteration are the while loop traversals, which are proportional to the number of nodes in the linked list, therefore the time complexity is O(n).
- The constant number of operations inside each loop and the absence of nested loops justify the linear time complexity.
Space Complexity:
- The algorithm creates a new node for each node in the original linked list, resulting in a linear increase in memory usage.
- No additional data structures are used, other than the extra nodes created to clone the linked list, which has a direct impact on the space complexity.
- Since the number of extra nodes created is proportional to the number of nodes in the original linked list, the space complexity is O(n).
Footnote
This question is rated as Medium difficulty.
Hints
Just iterate the linked list and create copies of the nodes on the go. Since a node can be referenced from multiple nodes due to the random pointers, ensure you are not making multiple copies of the same node.
You may want to use extra space to keep old_node —> new_node mapping to prevent creating multiple copies of the same node.
We can avoid using extra space for old_node —> new_node mapping by tweaking the original linked list. Simply interweave the nodes of the old and copied list. For example: Old List: A –> B –> C –> D InterWeaved List: A –> A’ –> B –> B’ –> C –> C’ –> D –> D’
The interweaving is done using next pointers and we can make use of interweaved structure to get the correct reference nodes for random pointers.
Similar Questions:
Title | URL | Difficulty |
---|---|---|
Clone Graph | https://leetcode.com/problems/clone-graph | Medium |
Clone Binary Tree With Random Pointer | https://leetcode.com/problems/clone-binary-tree-with-random-pointer | Medium |
Clone N-ary Tree | https://leetcode.com/problems/clone-n-ary-tree | Medium |