剑指 Offer 35. 复杂链表的复制
法一
我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 \(A \rightarrow B \rightarrow C\),我们可以将其拆分为 \(A \rightarrow A' \rightarrow B \rightarrow B' \rightarrow C \rightarrow C'\)。对于任意一个原节点 S,其拷贝节点 S' 即为其后继节点。
这样,我们可以直接找到每一个拷贝节点 S' 的随机指针应当指向的节点,即为其原节点 S 的随机指针指向的节点 T 的后继节点 T' 。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。
当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空。
/*
// 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) {
for (Node* p = head; p; p = p->next->next) {
Node* node = new Node(p->val);
node->next = p->next;
p->next = node;
}
for (Node* p = head; p; p = p->next->next) {
Node* node = p->next;
if (p->random)
node->random = p->random->next;
}
Node* newHead = new Node(-1);
Node* cur = newHead;
for (Node* p = head; p; p = p->next) {
cur->next = p->next;
cur = cur->next;
p->next = p->next->next;
}
return newHead->next;
}
};
法二
本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建。
如果不考虑 random 指针的话,对一条链表进行拷贝,我们只需要使用两个指针:一个用于遍历原链表,一个用于构造新链表(始终指向新链表的尾部)即可。这一步操作可看做是「创建节点 + 构建 next 指针关系」。
现在在此基础上增加一个 random 指针,我们可以将 next 指针和 random 指针关系的构建拆开进行:
先不考虑 random 指针,和原本的链表复制一样,创建新节点,并构造 next 指针关系,同时使用「哈希表」记录原节点和新节点的映射关系;
对原链表和新链表进行同时遍历,对于原链表的每个节点上的 random 都通过「哈希表」找到对应的新 random 节点,并在新链表上构造 random 关系。
有了哈希表,我们可以用\(O(1)\)的时间根据 S 找到 S'。
/*
// 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) {
unordered_map<Node*, Node*> mp;
Node* dummy = new Node(-1);
Node* cur = dummy;
for (Node* p = head; p; p = p->next) {
Node* node = new Node(p->val);
cur->next = node;
cur = cur->next;
mp[p] = node;
}
for (Node *p = head, *q = dummy->next; p; p = p->next, q = q->next) {
q->random = mp[p->random];
}
return dummy->next;
}
};