链表——随机链表的复制
设置指针的next域时一定要注意空指针访问错误
深拷贝是什么?
深拷贝 = 完完全全复制一份新的,跟原来的彻底没关系。
浅拷贝是什么?
只复制第一层,里面的对象还是共用原来的。
比如:
- 你有一个链表节点 A
- 浅拷贝出 A'
- A' 的 next、random 还是指向原来的节点
结果:
你改原链表 → 新链表也跟着变
解题思路
- 复制节点并拼接:在原链表每个节点后面插入一个值相同的新节点
- 复制随机指针:利用原节点的random指针,给新节点复制random指针
- 拆分链表:把原链表和新链表分离,得到最终的深拷贝链表
Java完整代码如下:
// 链表节点定义(题目给定)
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
public class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// 第一步:在每个原节点后面创建复制节点 1->1'->2->2'->3->3'
Node cur = head;
while (cur != null) {
// 创建新节点,值与原节点相同
Node copyNode = new Node(cur.val);
// 插入到原节点与下一个节点之间
copyNode.next = cur.next;
cur.next = copyNode;
// 移动到原链表的下一个节点
cur = copyNode.next;
}
// 第二步:设置复制节点的 random 指针
cur = head;
while (cur != null) {
// 新节点的random = 原节点random对应的复制节点
if (cur.random != null) {
cur.next.random = cur.random.next;
}
cur = cur.next.next; // 跳过复制节点,遍历原节点
}
// 第三步:拆分原链表和复制链表,得到深拷贝结果
Node newHead = head.next; // 新链表头节点
Node newCur = newHead;
cur = head;
while (cur != null) {
// 恢复原链表的next指针
cur.next = newCur.next;
cur = cur.next;
// 拼接新链表
if (cur != null) {
newCur.next = cur.next;
newCur = newCur.next;
}
}
return newHead;
}
}
代码逐段解析
- 边界判断
如果原链表为空,直接返回null,避免空指针异常。 - 第一步:插入复制节点
- 遍历原链表,对每个原节点cur,创建值相同的新节点copyNode
- 把copyNode插入到cur和cur.next之间
- 最终链表结构:原1 → 新1 → 原2 → 新2 → 原3 → 新3...
- 第二步:复制随机指针
- 原节点cur的random指向cur.random
- 那么新节点cur.next的random就指向cur.random.next(对应复制节点)
- 完美解决了随机指针无法直接复制的问题
- 第三步:拆分链表
- 把拼接后的链表拆分成原链表和新链表
- 新链表就是我们需要的深拷贝结果
- 同时不破坏原链表结构
复杂度分析
- 时间复杂度:O (n),只遍历链表 3 次,线性时间
- 空间复杂度:O (1),仅使用常数个临时变量,无额外哈希表空间
代码解释:
if (cur != null) {
newCur.next = cur.next;
newCur = newCur.next;
}
这一步不会少新尾节点的null,而是防止空指针访问错误,因为原来在插入新节点的时候,尾节点并没有指向原链表的节点,而是保留null的next指针域。因此最后也不需要对它的next域作处理
做题过程中出现的代码错误:
Node newHead = head.next;
Node newCur = newHead;
cur = head;
while(cur!=null){
cur.next = newHead.next; // ❌ 错误!这里应该用 newCur,不是 newHead
cur = cur.next;
if(cur!=null){
newHead.next = cur.next; // ❌ 错误!这里应该用 newCur,不是 newHead
newHead = newHead.next; // ❌ 错误!这里应该用 newCur,不是 newHead
}
}
return newHead;
你定义了 newCur 作为新链表的遍历指针,但全程没有使用,反而一直用 newHead(新链表的头节点)来操作。这会导致最后返回的newHead是新链表的尾节点而不是头节点。
关键修复说明:
变量名修正:把所有错误的 newHead 操作,全部改为 newCur。
- newHead:仅作为新链表的头节点,只在初始化和返回时使用,全程不修改。
- newCur:作为新链表的遍历指针,负责逐个拼接新节点,保证链表顺序正确。
浙公网安备 33010602011771号