题目描述如下
(https://leetcode.cn/problems/copy-list-with-random-pointer/)
对于一般链表的复制,无非就是申请空间->复制数据->链接,虽然这个的考点也是单向结构体如图(暂时先抹去random)
不看random的话就是再简单不过的单向链表了,因此起初最容易想到的办法就是先复制这个单向链表,再来考虑random的指向,代码:
if(head==NULL)
{
return NULL;
}
struct Node* tail = head;
//意义不明的哨兵位,请自行忽略
struct Node* newhead = (struct Node*) malloc(sizeof(struct Node));
newhead->next==NULL;
struct Node* ntail = newhead;
while(tail)//创建random为空的一个新链表
{
ntail->next= (struct Node*) malloc(sizeof(struct Node));
ntail=ntail->next;
ntail->val=tail->val;
ntail->next=ntail->random=NULL;
tail=tail->next;
}
对于复制好的链表,random为空,千万别犯了复制原链表中的random然后直接返回的错误,如果复制原来链表里的random的话就会是这样:
注意题目中的这一句
因此这样必然是错误的,那么“复制”random指针要怎么做到呢?最容易想到的必然是相对位置方法了,理解就是,比如下面这个,二号节点(13)的random指针指向一号节点(7),那么他指向的位置相对于整个链表的位置是一,因此,让复制来的链表也指向相同的相对位置就好了。
代码:
//暴力找出random节点
struct Node* Find_Random_Node(struct Node* head1,struct Node* head2, struct Node* random)
{
struct Node* phead1 = head1;
struct Node* phead2 = head2;
if(random==NULL)//random指向空直接返回就好了
{
return NULL;
}
//循环来判断每一个节点的random在链表中的位置
while(phead1!=random)
{
phead1=phead1->next;
phead2=phead2->next;
}
return phead2;
}
struct Node* copyRandomList(struct Node* head) {
if(head==NULL)
{
return NULL;
}
struct Node* tail = head;
//意义不明的哨兵位,请自行忽略
struct Node* newhead = (struct Node*) malloc(sizeof(struct Node));
newhead->next==NULL;
struct Node* ntail = newhead;
while(tail)//创建random为空的一个新链表
{
ntail->next= (struct Node*) malloc(sizeof(struct Node));
ntail=ntail->next;
ntail->val=tail->val;
ntail->next=ntail->random=NULL;
tail=tail->next;
}
//重置指针位置
tail=head;
ntail=newhead->next;
while(tail)//tail指到NULL时结束循环
{
//调用查抄函数,查找肯定离不开一个一个遍历
//通过相对位置来复制random指针
ntail->random = Find_Random_Node(head,newhead->next,tail->random);
tail=tail->next;
ntail=ntail->next;
}
struct Node* newlist = newhead->next;
free(newhead);
newhead=NULL;
return newlist;
}
以上代码确实实现了随机指针的复制,但是从时间复杂度上看,由于对每一个节点都要遍历,因此时间复杂度到达了O(N^2),有没有更高效的办法呢?
诶~当然有
通过边复制链表(基础的单向链表,先不考虑random指针)边插入进原链表对应节点的后面就能大大降低时间,可能不好从文字理解,先看图
此时只复制了第一个节点,当全部复制后,就会有:
有小伙伴会说了“你这样不就把原链表破坏了吗,不符合题目要求啊,而且random怎么解决呢?”
关于这几个问题,要知道,我们既然要返回复制后的链表,那么就要把复制的链表从原链表里提出来,然后链接,当然也要链接(还原原链表)。而random指针嘛,仔细观察会发现一个规律(除NULL外),复制节点的random指针应该指向的位置正好是对应原节点的random指针的下一个,既然如此那还不简单?附上代码:
struct Node* copyRandomList(struct Node* head)
{
struct Node* cur = head;
//复制节点并插入原链表对应节点之后
while(cur)
{
struct Node* copy = (struct Node*) malloc(sizeof(struct Node));
struct Node* next = cur->next;
copy->val=cur->val;
cur->next = copy;
copy->next = next;
cur=next;
}
cur = head;
//确定复制链表的random指向的相对位置与原链表相同
while(cur)
{
struct Node* copy_cur = cur->next;
if(cur->random==NULL)
{
copy_cur->random=NULL;
}
else
{
copy_cur->random=cur->random->next;
}
cur=copy_cur->next;
/*copy_cur=cur->next;*/ //猪鼻写错了,空指针问题来咯
}
//恢复两个链表
cur=head;
struct Node* CopyHead = NULL, * CopyTail=NULL;//复制链表的头尾
while(cur)
{
CopyTail=cur->next;
if(CopyHead==NULL)
{
CopyHead=CopyTail;
}
cur->next=CopyTail->next;
cur=cur->next;
if(cur!=NULL)//若只有一个元素+一个复制元素,cur会指向空
{
CopyTail->next=cur->next;
CopyTail=CopyTail->next;
}
}
return CopyHead;
}
首先,不难发现时间复杂度已下降至O(N)了,大大减少了数据过大时的时间;其次,这样写完全不用管传入的链表是否为空,美滋滋。