复制带有随机指针节点的链表
问题重述:
给你一个长度为 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指向对应的复制结点,由于复制节点的random指向的结点和原结点一样,那么我们可以使得原结点和复制节点一一映射,然后根据原结点的random确定复制节点的random(这就需要使用哈希表,一一映射)
还有一种方法,可以降低空间复杂度,那就是把复制节点直接连接在原结点后,然后复制节点的random可以直接等于原结点的random的下一个结点,这种方法只需要注意random可能为null即可
解法:
哈希表
解题:
代码:
常规:
时间复杂度:O(N),空间复杂度O(N)
public Node copyRandomList(Node head) {
if(head == null){
return head;
}
// 创建哈希表对象,并且创建一个结点用于记录头节点
HashMap<Node,Node> map = new HashMap();
Node cur = head;
// 循环链表,将链表元素加入到哈希表中
while(cur != null){
map.put(cur,new Node(cur.val));
cur = cur.next;
}
// 循环完成后,哈希表内键为原始结点,值为副本节点,指向全部为空
// 接下来遍历哈希表
cur = head;
while(cur != null){
// 现在的cur是哈希表内的键,我们可以根据这个值来操作哈希表
Node node = map.get(cur);
node.next = map.get(cur.next);
node.random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
代码解析:创建一个哈希表,哈希表的键和值的范围都是Node,因为都是用来存放链表结点的,因为我们是要对复制节点进行操作,因此我们原结点作为键,复制节点作为键,第一次循环,将源节点和复制节点添加到哈希表中,然后对链表进行第二次遍历,遍历过程中可以通过当前节点作为键找到复制节点,通过当前节点的random去确定复制节点的random。
进阶
时间复杂度O(N),空间复杂度O(1)
public Node copyRandomList(Node head) {
if(head == null){
return head;
}
// 复制结点
Node cur = head;
Node next = null;
while(cur != null){
// 循环链表,将每个节点复制一个新的放到自己的后面
next = cur.next;
// 创建一个副本结点,将该节点连接到当前节点和下一个节点中间
cur.next = new Node(cur.val);
cur.next.next = next;
cur = next;
}
// 循环结束后,链表是原结点和副本节点交替连接起来
// 再次循环链表,将副本节点的random联结起来
cur = head;
Node copyNode = null;
while(cur != null){
copyNode = cur.next;
// 这一步是将副本节点的random连接到对应的节点上去,需要注意节点的random可能为null
next = (cur.random == null ? null : cur.random.next);
copyNode.random = next;
// 更新cur
cur = copyNode.next;
}
// 此次循环结束后,所有副本节点的random都已经连接完毕,接下来就是将原链表和副本节点链表拆分开来
cur = head;
Node resHead = head.next;
while(cur != null){
copyNode = cur.next;
// 因为复制节点的下一个结点可能为null
next = (copyNode.next == null ? null : copyNode.next);
cur.next = next;
copyNode.next = (next == null ? null : next.next);
cur = next;
}
return resHead;
}
总结:
当遇见的问题需要一一映射的时候,我们就可以使用哈希表,一般数组用来排序或者进行数值的比较