Algorithm
- [🤖 问题](#🤖 问题)
-
- [🛠️ 要求与约束](#🛠️ 要求与约束)
- [🎯 常用解法概述(两种)](#🎯 常用解法概述(两种))
-
-
- [✅ 方法 A:三步原地交织法(推荐 --- O(n) 时间,O(1) 额外空间)](#✅ 方法 A:三步原地交织法(推荐 — O(n) 时间,O(1) 额外空间))
- [🧭 方法 B:哈希映射法(直观 --- O(n) 时间,O(n) 额外空间)](#🧭 方法 B:哈希映射法(直观 — O(n) 时间,O(n) 额外空间))
-
- [🧾 伪代码(方法 A:交织法)](#🧾 伪代码(方法 A:交织法))
- [🧾 C++ 实现(方法 A:交织法 --- 推荐)](#🧾 C++ 实现(方法 A:交织法 — 推荐))
- [🧠 详细逐步解释(方法 A)](#🧠 详细逐步解释(方法 A))
- [✅ 正确性说明](#✅ 正确性说明)
- [⏱️ 复杂度分析](#⏱️ 复杂度分析)
- [⚠️ 边界与注意事项](#⚠️ 边界与注意事项)
- [📚 其他补充(方法 B:哈希表示例简要)](#📚 其他补充(方法 B:哈希表示例简要))
🤖 问题
给你一个长度为 n
的链表,每个节点包含一个额外的随机指针 random
,该指针可以指向链表中的任何节点或 null
。请返回该链表的 深拷贝 (复制出一份完全独立的链表:节点值相同、next
和 random
指向复制链表中的新节点,且不指向原链表的节点)。
🛠️ 要求与约束
- 返回新链表的头节点(复制链表由
n
个全新节点组成)。 0 <= n <= 1000
,Node.val
在[-10^4,10^4]
。- 时间/空间要求:尽量做到O(n) 时间、O(1) 额外空间(不计返回的新链表空间)。
🎯 常用解法概述(两种)
✅ 方法 A:三步原地交织法(推荐 --- O(n) 时间,O(1) 额外空间)
思路分三步(经典且高效):
- 在每个原节点后面插入对应的新节点 :例如原链
A -> B -> C
变成A -> A' -> B -> B' -> C -> C'
(A' 为 A 的拷贝)。 - 设置新节点的 random 指针 :对每个原节点
node
,如果node.random != null
,则node.next.random = node.random.next
(因为node.next
是 node 的拷贝,node.random.next
是 node.random 的拷贝)。 - 拆分链表 :把交织链表分成原链和新链,恢复原链
next
指针,并把新节点串成独立链表返回。
优点:不需要额外哈希表,空间为常数(除了输出本身)。
🧭 方法 B:哈希映射法(直观 --- O(n) 时间,O(n) 额外空间)
- 使用
unordered_map<原节点指针, 新节点指针>
。 - 第一次遍历创建每个原节点的拷贝并放进 map;第二次遍历设置每个拷贝的
next
与random
指针(映射 lookup)。 - 更直观但使用 O(n) 额外空间。
🧾 伪代码(方法 A:交织法)
function copyRandomList(head):
if head is null: return null
# 1) 在每个原节点后插入拷贝节点
p = head
while p != null:
newNode = Node(p.val)
newNode.next = p.next
p.next = newNode
p = newNode.next
# 2) 设置拷贝节点的 random 指针
p = head
while p != null:
if p.random != null:
p.next.random = p.random.next
# else p.next.random stays null
p = p.next.next # 跳到下一个原节点
# 3) 拆分成两个链表(原链恢复,拷贝链独立)
p = head
copyHead = head.next
while p != null:
copy = p.next
p.next = copy.next # 恢复原节点的 next
if copy.next != null:
copy.next = copy.next.next
p = p.next
return copyHead
🧾 C++ 实现(方法 A:交织法 --- 推荐)
cpp
#include <iostream>
using namespace std;
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) : val(_val), next(nullptr), random(nullptr) {}
};
class Solution {
public:
Node* copyRandomList(Node* head) {
if (!head) return nullptr;
// 1) 在每个原节点后面插入其拷贝
Node* p = head;
while (p) {
Node* copy = new Node(p->val);
copy->next = p->next;
p->next = copy;
p = copy->next;
}
// 2) 复制 random 指针
p = head;
while (p) {
if (p->random) {
p->next->random = p->random->next;
}
p = p->next->next; // 跳到下一个原节点
}
// 3) 拆分链表:恢复原链并提取拷贝链
p = head;
Node* copyHead = head->next;
while (p) {
Node* copy = p->next;
p->next = copy->next; // 恢复原链的 next
p = p->next; // 移动到下一个原节点
if (p) copy->next = p->next; // 设置复制节点的 next(可能为 null)
}
return copyHead;
}
};
🧠 详细逐步解释(方法 A)
- 第一步插入 :对
p
(原节点)做p.next = new Node(p.val)
,并把new.next
指向原p.next
。这样每个原节点后面紧跟着其拷贝。 - 第二步 random 复制 :因为每个原节点
p
的拷贝是p.next
,而p.random
的拷贝是p.random.next
(若p.random!=null
)。所以直接p.next.random = p.random.next
即可。 - 第三步拆分 :当前链:
p -> p' -> q -> q' -> ...
。要恢复原链p->q->...
(即p.next = p'.next
),并构建拷贝链p'->q'->...
(即p'.next = q'.next
,实现为if (p) copy->next = p->next
)。遍历结束后得到独立的拷贝链。
✅ 正确性说明
- 插入后的结构保证每个原节点后紧跟其拷贝,
random
指针复制时只需常数时间定位对应拷贝; - 拆分时逐个恢复原节点的
next
并连接拷贝节点的next
,最终两链都正确且互不指向对方的内存; - 每个原节点、新节点只被常量次操作,故整体线性时间。
⏱️ 复杂度分析
-
时间复杂度 :O(n),三次遍历(插入、复制
random
、拆分)各 O(n)。 -
空间复杂度(额外):O(1),只使用若干临时指针和常数额外变量;新节点内存计入输出,不算作额外空间。
对比哈希法:时间 O(n),空间 O(n)。
⚠️ 边界与注意事项
- 输入
head == nullptr
时返回nullptr
。 - 链表中
random
可能为null
,处理时要判空。 - 错误写法容易在拆分时访问空指针,拆分循环写法要小心:
p = p->next
必须跟随恢复后的原链结构。 - 保证
new
创建的节点在拆分时被串成新的链并最终返回。
📚 其他补充(方法 B:哈希表示例简要)
若不要求 O(1) 额外空间,可用 unordered_map<Node*, Node*> mp
:
- 遍历原链,创建每个
orig -> copy
的映射; - 再遍历一次,用
mp[orig]->next = mp[orig->next]
(若orig->next
存在)和mp[orig]->random = mp[orig->random]
(若存在)设置指针; - 返回
mp[head]
。