题目
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
分析
题目要求把原链表复制一份出来,难点在于节点不仅有next域,还有random域,而random的指向并不是顺序的,所以需要一些技巧
思路1 - 节点副本
在每个结点后面复制出一个节点副本,并对新的节点维护random关系,依次将节点副本连接起来,即为新链表。
需要遍历一遍链表,时间复杂度是 O ( n ) O(n) O(n)
思路2 - 哈希
如前所述,此题难点在于random的指向并不是顺序的,所以顺次访问结点时,可能结点的random指针指向一个还不存在的节点,所以应该在访问前创建好节点,由此可知,创建节点的顺序并不是链表的节点顺序。
可以使用哈希表建立新链表与旧链表节点之间的映射,key为旧链表节点,value为对应的新链表节点,遍历链表时,如果节点的哈希值不存在或者其random节点的哈希值不存在,则创建新节点,否则说明该结点已创建,只要维护next和random关系就好。这样就能综合考虑原节点次序以及random节点次序,合理创建新的节点。
同样需要遍历一遍链表,时间复杂度是 O ( n ) O(n) O(n)
代码实现(cpp)
思路1 - 节点副本 O ( n ) O(n) O(n)
class Solution {
public:
Node* copyRandomList(Node* head) {
Node* cur = head;
// 每个结点复制出一个新节点
while (cur) {
Node* tmp = cur->next;
Node* copy = new Node(cur->val);
cur->next = copy;
copy->next = tmp;
cur = tmp;
}
// 维护random关系
cur = head;
while (cur) {
if (cur->random != nullptr) cur->next->random = cur->random->next;
cur = cur->next->next;
}
Node* res = new Node(-1);
Node* p = res; //新链接当前节点
cur = head;
while (cur) {
p->next = cur->next;
p = p->next;
cur->next = cur->next->next; //恢复原链表
cur = cur->next;
}
return res->next;
}
};
思路2 - 哈希 O ( n ) O(n) O(n)
class Solution {
public:
Node* copyRandomList(Node* head) {
unordered_map<Node*, Node*> mp;
Node* res = new Node(-1);
Node* cur = head; // 遍历旧链表的指针
Node* p = res; // 新链表当前节点
mp[nullptr] = nullptr; //因为某些节点的random节点可能是空,防止访问出错
while (cur) {
if (!mp.count(cur)) mp[cur] = new Node(cur->val); //复制出一个新节点
if (!mp.count(cur->random)) mp[cur->random] = new Node(cur->random->val); // 复制出其random节点
p->next = mp[cur];
p->next->random = mp[cur->random];
p = p->next;
cur = cur->next;
}
return res->next;
}
};
总结
这两种做法的时间复杂度都是 O ( n ) O(n) O(n),应该还可以优化,之后再更新