1. 题目来源
链接:复杂链表的复制
来源:LeetCode——《剑指-Offer》专项
2. 题目说明
请实现 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。
提示:
- -10000 <= Node.val <= 10000
- Node.random 为空(null)或指向链表中的节点。
- 节点数目不超过 1000 。
3. 题目解析
方法一:辅助HashMap+迭代解法
这道题目的难点在于如何处理随机指针的问题,由于每一个节点都有一个随机指针,这个指针可以为空,也可以指向链表的任意一个节点,如果在每生成一个新节点给其随机指针赋值时,都要去遍历原链表的话,OJ
上很有可能会 TLE
,所以可以考虑用 HashMap
来缩短查找时间,第一遍遍历生成所有新节点时同时建立一个原节点和新节点的 HashMap
,第二遍给随机指针赋值时,查找时间是常数级。
参见代码如下:
// 执行用时 :8 ms, 在所有 C++ 提交中击败了94.82%的用户
// 内存消耗 :13.6 MB, 在所有 C++ 提交中击败了100.00%的用户
/*
// 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) {
if (!head) return nullptr;
Node *res = new Node(head->val);
Node *node = res, *cur = head->next;
unordered_map<Node*, Node*> m;
m[head] = res;
while (cur) {
Node *t = new Node(cur->val, nullptr, nullptr);
node->next = t;
m[cur] = t;
node = node->next;
cur = cur->next;
}
node = res; cur = head;
while (cur) {
node->random = m[cur->random];
node = node->next;
cur = cur->next;
}
return res;
}
};
方法二:辅助HashMap+递归解法
我们可以使用递归的解法,写起来相当的简洁,还是需要一个 HashMap
来建立原链表结点和拷贝链表结点之间的映射。在递归函数中,首先判空,若为空,则返回空指针。然后就是去 HashMap
中查找是否已经在拷贝链表中存在了该结点,是的话直接返回。否则新建一个拷贝结点 res
,然后建立原结点和该拷贝结点之间的映射,然后就是要给拷贝结点的 next
和 random
指针赋值了,直接分别调用递归函数即可。
参见代码如下:
class Solution {
public:
Node* copyRandomList(Node* head) {
unordered_map<Node*, Node*> m;
return helper(head, m);
}
Node* helper(Node* node, unordered_map<Node*, Node*>& m) {
if (!node) return nullptr;
if (m.count(node)) return m[node];
Node *res = new Node(node->val, nullptr, nullptr);
m[node] = res;
res->next = helper(node->next, m);
res->random = helper(node->random, m);
return res;
}
};
方法三:非辅助空间+ O ( n ) O(n) O(n)时间效率+巧妙解法
这个解法在初学链表时给我留下了深刻的印象,没想到在此又遇到这个题,这也是《剑指-Offer》上所推荐的解法。分为三步:
-
将新复制出来的链表节点就链到原链表节点的后面
-
-
原链表的
random
节点所在位置的next
就是拷贝链表节点的random
节点位置 -
-
将这个长链表拆为两个链表,即奇数位置就是拷贝链表
-
很经典的思路啊,完美的展现了链表唯一确定下一个节点的特性!
参见代码如下:
// 执行用时 :8 ms, 在所有 C++ 提交中击败了94.82%的用户
// 内存消耗 :13.4 MB, 在所有 C++ 提交中击败了100.00%的用户
/*
// 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) {
if (head == nullptr) return nullptr;
Node* cur = head;
while (cur != nullptr) {
Node* node = new Node(cur->val);
node->next = cur->next;
cur->next = node;
cur = cur->next->next;
}
cur = head;
while (cur != nullptr) {
if (cur->random != nullptr) cur->next->random = cur->random->next;
cur = cur->next->next;
}
Node* copyHead = head->next;
cur = head;
Node * copy = head->next;
while (cur != nullptr) {
cur->next = cur->next->next;
cur = cur->next;
if (copy->next != nullptr) {
copy->next = copy->next->next;
copy = copy->next;
}
}
return copyHead;
}
};