剑指 Offer 35. 复杂链表的复制 试试这题
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 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]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
整体感知:这题很特殊,让我们不舒服的地方就是链表的结点变成了三个部分组成在原来单链表的基础上又加上了random指针,这个指针指向任意的结点或者指向NULL都有可能,这就导致我们在复制这个单链表的基础上又增添了不少的麻烦。
有些同学会想会这样想:我就麻烦一点做,原链表有几个结点我就开辟几个结点,并且都附上各自对应结点的val,至于random指针可以在遍历一遍链表,然后看原链表的结点指向那个节点,新开辟的结点就指向哪个节点。
这样去做其实没有特别大的问题,但是这样做有一个致命的缺点,如果是像:1->2->3->4->NULL这样的链表的这样做还行,要是1->2->3->3->3->-3->4->NULL这样的链表,你怎么办呢?因为val=3的结点有不止一个,所以在遍历链表的时候,根本就没法知道那个节点对应哪个节点的地址,所以只用val的值来判断并复制结点是不可靠的。
那么我借来分享一下我的想法:
1.在原链表中的每个结点的后面添加一个节点的cpoy版本(也就是原结点一样)(这是val的复制就已经完成了)
2.解决random的值,(原节点的copy)->random = 原节点->random->next
上面这个代码不理解没关系,我会画图给大家解释
3.将已经复制好的结点从原链表中拆解下来
第一步在原节点后面插入结点
第二步(关键一步)将找到复制结点的random,假设原结点用cur来表示
copy->random = cur->random->next
用语言来说就是原结点的copy的random是原结点的random的后面的那个节点的地址
举个栗子,假设结点分别加ABCDEFGHIJ,先看F结点的random就是A结点的random(C结点)的后面的那个结点(F结点),这样是不是就正确了,再看一个,J结点的random就是E结点的random(还是E结点)的后面的那个结点(J结点本身),所以J结点指向自己,但是还有一种特殊情况,就是E结点指向空,所以I结点依然指向空就行了。
3.最后将每个结点拆解下来
这样就完成了全部过程了
下面还是来肝代码
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == NULL)
return NULL;
//1.创建复制结点加到链表中间
Node* cur = head;
while(cur)
{
//完成结点的初始化,并复制val
Node* copy = (Node*)malloc(sizeof(Node));
copy->next = NULL;
copy->random = NULL;
copy->val = cur->val;
Node* next = cur->next;
cur->next = copy;
copy->next = next;
cur = next;//cur进行迭代,当cur到NULL的时候就说明遍历了一遍链表皆可以停下来了
}
//random的复制
cur = head;
while(cur)
{
Node* copy = cur->next;
Node* next = copy->next;
if(cur->random)//避免cur的random指向NULL
copy->random = cur->random->next;
else
copy->random = NULL;
cur = next;
}
cur = head;
Node* copyhead = cur->next;//保留新链表的头
//结点从链表上拆解下来
while(cur)
{
Node* copy = cur->next;
Node* next = copy->next;
cur->next = next;
if(next)//因为最后一个结点指向空,所以这比较特殊直接指向NULL
copy->next = next->next;
else
copy->next = NULL;
cur = next;
}
return copyhead;
}
};
根据写完代码,可以看出除了画图解决的思路之外,还有几个点要注意
1.当head指向NULL,直接返回NULL
2.当原来结点的random指向空,要判断一下
3.当拆解链表的时候,到最后直线原结点的后面的结点,但是原来的结点的最后是NULL,NULL不能解引用,所以要判断一下直接让最后直线NULL即可