随机链表的复制
题目描述:
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
示例 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]]
提示:
0 <= n <= 1000
-104 <= Node.val <= 104
Node.random
为null
或指向链表中的节点。
思路分析:
首先遍历链表,为每个节点创建一个复制节点并插入到其后。然后,再次遍历链表,设置复制节点的 random
指针。最后,通过遍历拆分链表,得到完整的复制链表,并返回其头节点。为了解决这个问题,我们可以采用三步走的方法:
-
复制节点并插入到原节点之后:遍历原链表,对于每个节点,创建一个新的节点,其值与原节点相同,然后将新节点插入到原节点之后。
// Step 1: 复制节点并插入到原节点之后
Node curr = head;
while (curr != null) {
Node copy = new Node(curr.val); // 创建新节点,其值与当前节点相同
copy.next = curr.next; // 新节点的next指向当前节点的下一个节点
curr.next = copy; // 将当前节点的next指向新节点,完成插入
curr = copy.next; // 移动到下一个原始节点
}
2.设置复制节点的 random
指针:由于复制节点紧跟在原节点之后,我们可以通过遍历原链表,同时访问对应的复制节点,来设置复制节点的 random
指针。
// Step 2: 设置复制节点的random指针
curr = head;
while (curr != null) {
if (curr.random != null) { // 如果当前节点的random不为空
curr.next.random = curr.random.next; // 则将当前节点的复制节点的random指向对应原始节点的random的下一个节点(即复制节点)
}
curr = curr.next.next; // 跳过当前节点和其复制节点,移动到下一个原始节点
}
3.拆分链表:最后,我们需要将原链表和复制链表拆分,得到完整的复制链表。
// Step 3: 拆分原始链表和复制链表
Node dummy = new Node(0); // 创建一个哑节点作为复制链表的头节点
Node copyTail = dummy; // copyTail用于追踪复制链表的尾部
curr = head;
while (curr != null) {
Node nextOriginal = curr.next.next; // 保存当前原始节点的下一个原始节点
copyTail.next = curr.next; // 将当前原始节点的复制节点链接到复制链表的尾部
copyTail = copyTail.next; // 更新复制链表的尾部
curr.next = nextOriginal; // 恢复原始链表的链接(断开复制节点)
curr = nextOriginal; // 移动到下一个原始节点
}
代码实现:
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
// 如果链表为空,则直接返回null
return null;
}
// Step 1: 复制节点并插入到原节点之后
Node curr = head;
while (curr != null) {
Node copy = new Node(curr.val); // 创建新节点,其值与当前节点相同
copy.next = curr.next; // 新节点的next指向当前节点的下一个节点
curr.next = copy; // 将当前节点的next指向新节点,完成插入
curr = copy.next; // 移动到下一个原始节点
}
// Step 2: 设置复制节点的random指针
curr = head;
while (curr != null) {
if (curr.random != null) { // 如果当前节点的random不为空
curr.next.random = curr.random.next; // 则将当前节点的复制节点的random指向对应原始节点的random的下一个节点(即复制节点)
}
curr = curr.next.next; // 跳过当前节点和其复制节点,移动到下一个原始节点
}
// Step 3: 拆分原始链表和复制链表
Node dummy = new Node(0); // 创建一个哑节点作为复制链表的头节点
Node copyTail = dummy; // copyTail用于追踪复制链表的尾部
curr = head;
while (curr != null) {
Node nextOriginal = curr.next.next; // 保存当前原始节点的下一个原始节点
copyTail.next = curr.next; // 将当前原始节点的复制节点链接到复制链表的尾部
copyTail = copyTail.next; // 更新复制链表的尾部
curr.next = nextOriginal; // 恢复原始链表的链接(断开复制节点)
curr = nextOriginal; // 移动到下一个原始节点
}
return dummy.next; // 返回复制链表的头节点(哑节点的下一个节点)
}
}