文章目录
-
- 一、题目简介
- 二、节点结构说明
- 三、问题难点分析
- 四、解法一:哈希表映射(最直观解法)
- [五、解法二:原地复制(O(1) 额外空间,面试高频)](#五、解法二:原地复制(O(1) 额外空间,面试高频))
- 步骤一:复制节点并插入
-
- 示例图
- [Java 实现(步骤一)](#Java 实现(步骤一))
- [步骤二:设置 random 指针](#步骤二:设置 random 指针)
-
- 关键观察
- [Java 实现(步骤二)](#Java 实现(步骤二))
- 步骤三:拆分链表
-
- [Java 实现(步骤三)](#Java 实现(步骤三))
- [六、完整 Java 代码(O(1) 空间解法)](#六、完整 Java 代码(O(1) 空间解法))
- 七、复杂度分析(原地解法)
- 八、两种解法对比总结
一、题目简介
给你一个长度为 n 的链表,每个节点包含两个指针:
next:指向下一个节点random:指向链表中的任意节点或null
要求你深拷贝这个链表,并返回复制后的链表头节点。
深拷贝意味着:
- 新链表中的节点是全新创建的
next和random的指向关系与原链表完全一致- 不能复用原链表的任何节点
二、节点结构说明
java
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
三、问题难点分析
与普通链表拷贝不同,本题的难点在于:
random指针可能:- 指向前面的节点
- 指向后面的节点
- 指向自身
- 为
null
- 拷贝时,必须确保 random 指向的是"新节点"而不是旧节点
- random 的指向关系在拷贝过程中并不具备天然的"先后顺序"
四、解法一:哈希表映射(最直观解法)
核心思想
旧节点 → 新节点,一一映射
使用 HashMap<Node, Node> 保存原节点与复制节点的对应关系。
解题步骤
第一步:复制所有节点(只处理 val)
text
原链表: A -> B -> C
复制后: A' -> B' -> C'
建立映射关系:
A -> A'
B -> B'
C -> C'
第二步:处理 next 和 random 指针
newNode.next = map.get(oldNode.next)newNode.random = map.get(oldNode.random)
过程示意(时序图)
新链表节点 HashMap 原链表节点 新链表节点 HashMap 原链表节点 put(OldNode, NewNode) get(OldNode.next) get(OldNode.random) 设置 next / random
Java 实现
java
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
Map<Node, Node> map = new HashMap<>();
// 1. 复制节点
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
// 2. 复制指针
cur = head;
while (cur != null) {
Node newNode = map.get(cur);
newNode.next = map.get(cur.next);
newNode.random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n)(HashMap) |
优缺点总结
✅ 优点
- 思路直观
- 容易实现
- 不易出错
❌ 缺点
- 需要额外 O(n) 空间
五、解法二:原地复制(O(1) 额外空间,面试高频)
核心思想
把复制节点直接插入到原节点后面
利用链表结构本身,避免使用额外的哈希表。
整体流程概览
原链表
复制节点并插入原节点后
设置复制节点的 random 指针
拆分成原链表和新链表
步骤一:复制节点并插入
text
原链表:
A -> B -> C
插入后:
A -> A' -> B -> B' -> C -> C'
示例图
A
A1
B
B1
C
C1
Java 实现(步骤一)
java
Node cur = head;
while (cur != null) {
Node copy = new Node(cur.val);
copy.next = cur.next;
cur.next = copy;
cur = copy.next;
}
步骤二:设置 random 指针
关键观察
如果:
cur.random = X
那么:
cur.next.random = X.next
因为 X.next 正好是 X 的复制节点。
Java 实现(步骤二)
java
cur = head;
while (cur != null) {
if (cur.random != null) {
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
步骤三:拆分链表
将交织在一起的链表拆分成:
- 原链表
- 新链表
Java 实现(步骤三)
java
Node dummy = new Node(0);
Node copyCur = dummy;
cur = head;
while (cur != null) {
copyCur.next = cur.next;
cur.next = cur.next.next;
copyCur = copyCur.next;
cur = cur.next;
}
return dummy.next;
六、完整 Java 代码(O(1) 空间解法)
java
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
Node cur = head;
// 1. 复制节点并插入
while (cur != null) {
Node copy = new Node(cur.val);
copy.next = cur.next;
cur.next = copy;
cur = copy.next;
}
// 2. 设置 random
cur = head;
while (cur != null) {
if (cur.random != null) {
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
// 3. 拆分链表
Node dummy = new Node(0);
Node copyCur = dummy;
cur = head;
while (cur != null) {
copyCur.next = cur.next;
cur.next = cur.next.next;
copyCur = copyCur.next;
cur = cur.next;
}
return dummy.next;
}
七、复杂度分析(原地解法)
| 指标 | 复杂度 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(1) |
八、两种解法对比总结
| 方案 | 时间复杂度 | 空间复杂度 | 难度 | 适用场景 |
|---|---|---|---|---|
| HashMap | O(n) | O(n) | ⭐⭐ | 快速实现 |
| 原地复制 | O(n) | O(1) | ⭐⭐⭐⭐ | 面试高频 |