
用很白话的话说,这题就是:
有一条「很特别的链表」,每个节点不止有 next(下一个),还有一个 random(随便指向谁)。
你要再造一条一模一样的新链表,但:
-
新链表里的每个节点,都是新的对象(不能直接用原来的节点)
-
新链表的结构(next 链接)要和原来一模一样
-
新链表里的 random 关系也要和原来一模一样
比如:原来 A.random 指向 B,那新链表里的 a.random 就要指向 b
关键点有三个:
-
每个节点有两个指针
-
next:正常链表的下一个 -
random:可以指向-
链表里的任意一个节点
-
或者是
null
-
-
-
要做的是"深拷贝"
-
不能只复制数值和指针引用到原节点,那叫"浅拷贝"
-
要重新 new 出 n 个新节点
-
新节点的
val跟原节点一样 -
新节点的
next和random也串成同样的结构,但只能指向新链表中的节点
-
-
返回新链表的头结点
最后你要返回的是"复制出来的那条链表"的头,而不是原来的。
用个小例子说明一下:
原链表(逻辑上):
-
节点0:值 7,random -> null
-
节点1:值 13,random -> 节点0
-
节点2:值 11,random -> 节点4
-
...
你要造一条新的链:
-
新节点0:值 7,random -> 新节点里的 null
-
新节点1:值 13,random -> 新节点0
-
新节点2:值 11,random -> 新节点4
-
...
结构关系一模一样,但"人"都换成新造的。
如果你现在明白题意了,下一步就是:怎么把旧节点和新节点对应起来并设置 random,这就是解法要做的事了。
核心难点
普通的链表拷贝只需要遍历并创建 next 节点即可。但这道题的难点在于 random 指针可能指向尚未创建的节点 ,或者指向前面已经过去的节点。
解决方案:哈希表法(最通用、易理解)
利用哈希表(unordered_map)建立 原节点地址 到 新节点地址 的映射关系。
-
第一轮遍历 :遍历原链表,只创建新节点(复制
val),并将<原节点指针, 新节点指针>存入哈希表。此时不处理指针连接。 -
第二轮遍历 :再次遍历原链表,通过哈希表找到对应的 新节点,并根据原节点的
next和random指向,在哈希表中查找对应的新节点进行连接。
C++
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
#include <unordered_map>
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == nullptr) {
return nullptr;
}
// 哈希表:Key是原节点地址,Value是新节点地址
std::unordered_map<Node*, Node*> map;
// 第一步:遍历原链表,创建新节点,并建立映射关系
Node* cur = head;
while (cur != nullptr) {
map[cur] = new Node(cur->val);
cur = cur->next;
}
// 第二步:再次遍历,构建新节点的 next 和 random 指针
cur = head;
while (cur != nullptr) {
// map[cur] 是新节点
// map[cur->next] 是原节点 next 指向的那个节点 对应的 新节点
if (cur->next != nullptr) {
map[cur]->next = map[cur->next];
}
// 同理处理 random
if (cur->random != nullptr) {
map[cur]->random = map[cur->random];
}
cur = cur->next;
}
// 返回哈希表中存储的 head 对应的新头节点
return map[head];
}
};
复杂度分析
-
时间复杂度:O(N)。我们需要遍历链表两次,哈希表的查找操作是 O(1) 的,所以总时间是线性的。
-
空间复杂度:O(N)。我们需要一个哈希表来存储 N 个节点的映射关系。