数据结构题解——复制带随机指针的链表

题目描述如下

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)了,大大减少了数据过大时的时间;其次,这样写完全不用管传入的链表是否为空,美滋滋。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值