复杂链表的复制(题号35)
问题描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。
示例:
输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}
解析:我们将链表分为两段,前半部分{1,2,3,4,5}为
ListNode
,后半部分{3,5,#,2,#}是随机指针域表示。以上示例前半部分可以表示链表为的
ListNode
:1->2->3->4->5后半部分3,5,#,2,#分别表示为:1的位置指向3,2的位置指向5,3的位置指向
null
,4的位置指向2,5的位置指向null
解法:
hash
表辅助存储法思路:遍历原链表,在遍历的过程中判断原链表的当前节点是否存在复制节点,存在将复制链表的前序节点
next
指针指向该复制的节点(从hash
表取出),不存在则以当前节点的内容创建新的复制节点,复制链表的前序节点的next
指针指向它,并将节点地址映射关系存储进hash
表。其次判断原链表的当前节点的random
指针指向的节点是否存在复制节点,存在将复制链表的前序节点的next
节点的random
指针指向该复制的节点(从hash
表取出),不存在则以当前节点的random
节点内容创建新的复制节点,复制链表的前序节点的next节点的random
指针指向它,并将节点地址映射关系存储进hash
表。原链表的当前节点curr
和复制链表的前序节点prev
步进,循环以上的操作直到curr
为null
时间复杂度:O(n), 遍历一次原链表的时间
空间复杂度:O(n), 哈希表使用的空间注意事项:
- 题意为深拷贝,所以复制链表的节点不能有指向原链表同位置节点的指针
- 特例情况的处理,这里要为复制链表创建虚拟头结点好为前序节点
prev
初始赋值链表拼接、拆分
思路:这种方法需要遍历三次链表。定义一个游标指针
curr
,指向原链表的头结点,第一次遍历原链表,遍历的过程中将经过的节点复制并链接到被复制节点的后面:原节点->复制节点->原节点的后续节点
,curr
指针步进,待遍历完整个链表,此时链表的节点数翻倍,一前一后包含原节点和复制出的新节点。将curr
指针复位到头结点,第二次遍历更新过后的链表,这次是将复制节点的random
指针赋值,判断当前节点的random
节点是否为空,如果不为空则将当前节点的后续节点的random
指针指向为当前节点的random
节点的后续节点。来到第三次遍历,此时跟第二次遍历一样,定义三个指针p1
、p2
和复制出的链表的头结点指针newHead
,p1
指向当前链表的头节点,p2
和newHead
指向头结点的后续节点,p1
、p2
的后续节点指向为它们后续节点的后续节点,完后分别步进到新的后续节点,当p2
的后续节点不为空的时候如此往复。待遍历完后将p1
的后续节点指向空(null
)完成拆分,返回复制链表的头结点newHead
时间复杂度:O(n), 遍历了三次链表
空间复杂度:O(1), 使用了常数个链表指针变量注意事项:
- 第三次遍历完后注意将链接原链表的游标指针指向
null
解法一
图示:
image代码
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
// 原链表的当前节点初始赋值
RandomListNode curr = pHead;
// 伪造一个头节点,避免处理特例情况
RandomListNode dummyHead = new RandomListNode(Integer.MIN_VALUE);
// 前序节点初始赋值
RandomListNode prev = dummyHead;
// 创建一个hash表,用于存储原节点和复制节点的映射关系,例如A->A'
Map<RandomListNode, RandomListNode> map = new HashMap<>();
// 遍历原链表
while (curr != null) {
// 如果hash表中不存在原链表当前节点的复制节点则以当前节点的内容创建新的复制节点,复制链表的前序节点的next指针指向它,并将节点地址映射关系存储进hash表
if (!map.containsKey(curr)) {
prev.next = new RandomListNode(curr.val);
map.put(curr, prev.next);
} else {// 如果hash表中存在原链表当前节点的复制节点,则复制链表的前序节点的next指针指向该复制节点
prev.next = map.get(curr);
}
// 如果hash表中不存在原链表当前节点的random指针指向节点的复制节点则以当前节点的random节点内容创建新的复制节点,复制链表的前序节点的next节点的random指针指向它,并将节点地址映射关系存储进hash表
if (!map.containsKey(curr.random) && curr.random != null) {
prev.next.random = new RandomListNode(curr.random.val);
map.put(curr.random, prev.next.random);
} else {// 如果hash表中存在原链表当前节点的random节点的复制节点,则复制链表的前序节点的next节点的random指针指向该复制节点
prev.next.random = map.get(curr.random);
}
// 步进原链表的当前节点
curr = curr.next;
// 步进复制链表的前序节点
prev = prev.next;
}
// 返回复制链表的真实头结点
return dummyHead.next;
}
}
解法二
图示
image代码
/*
class RandomListNode {
RandomListNode(int val) {
this.val = val;
}
int val;
RandomListNode next;
RandomListNode random;
}
*/
public class Solution {
public RandomListNode copyRandomList(RandomListNode pHead) {
if (pHead == null) {
return null;
}
RandomListNode curr = pHead;
while (curr != null) {
RandomListNode newNode = new RandomListNode(curr.val);
newNode.next = curr.next;
curr.next = newNode;
curr = newNode.next;
}
curr = pHead;
while (curr != null) {
if (curr.random != null) {
curr.next.random = curr.random.next;
}
curr = curr.next.next;
}
RandomListNode newHead = pHead.next;
RandomListNode p1 = pHead;
RandomListNode p2 = p1.next;
while (p2.next != null) {
p1.next = p1.next.next;
p2.next = p2.next.next;
p1 = p1.next;
p2 = p2.next;
}
// 注意断开原链表最末节点和新链表最末节点的连接
p1.next = null;
return newHead;
}
}