【链表题解】力扣138. 复制带随机指针的链表

目录

一、题目

二、思路

方法一:

方法二:

1.复制节点

2.设置random

3.分离链表 

三、代码

方法一:

方法二:

结尾


ID:HL_5461 

一、题目

题目链接:

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目分析:

如果只是单纯的复制链表其实没多难,难点在于复制的链表节点带有随机指针random,而且复制链表中的指针都不应指向原链表中的节点 。这需要我们需要清楚的知道每个原节点所对应的复制节点的位置。知道位置我们挨个遍历每个复制节点然后令它们的random指向相应的位置就可以了。


二、思路

方法一:

该方法 时间复杂度O(N^2),空间复杂度O(1)

将原链表视作普通单链表进行复制这样我们就拥有两条链表啦~当然,我一般喜欢复制时加一个哨兵位的头结点,不加也可以,但要加一步判断是否为头结点。总之,个人喜好吧,加了……嗯,很香!

创建四个指针变量:cur和curcopy分别遍历两个链表,find1和find2用于找random所在位置。四个指针初始位置分别为所在链表的第一个结点。

 

(别找不同了,这还是上面那张图)

此时,cur->random为NULL,那么也令curcopy的random为NULL。修改完后令cur和curcopy都移向对应的next节点。同时注意将find1和find2都指回对应链表的第一个结点(因为这里find1和find2没移动,所以指回头结点在我们看来就跟没操作一样)。

此时cur->next != NULL,我们开始移动find1,令find1去找random,find1每移一步,find2也移动一步。

(因上一幅图cur->random为第一个结点,看不出两个find指针的移动效果,我们这里假设前面已经处理完成,此时cur指向如图第三个结点,然后移动find1指针,并移动find2指针)

 直到find1 == cur->random,此时find2的位置为复制链表对应的random位置。令curcopy->random = find2。

如图,当cur == NULL时循环结束。因为是一个指针cur遍历列表的循环中嵌套了一个find1找random的循环,所以该方法的时间复杂度为O(N^2),没有开辟额外空间(复制开辟的新链表不算额外空间),空间复杂度为O(1)。(时间复杂度看不懂没关系,后面看代码就清楚了)。最后,free哨兵位节点,返回哨兵位头节点的后一位,即真正的第一个结点。

方法二:

该方法 时间复杂度O(N),空间复杂度O(1)

这个方法真的很妙,但是真的不容易想也不容易理解。我们分为三部分说明。

1.复制节点

使用malloc开辟新节点,修改新结点的next指针和cur的next指针,使得新结点能插入原链表。

next和cur均向后移(cur = next,next = cur->next),然后重复上面插入结点的步骤。

直到每个原结点都拥有了在自己后面的copy节点,此时循环停止。由此循环终止的条件为cur == NULL。估计有人会纠结next怎么办,因为next是cur在原链表的下一个嘛,此时cur为NULL,也就说next = NULL->next,显然是有问题的。但咱不妨换个思路,把next = cur->next这条语句放到循环的第一句,也就说每次进循环先执行这条语句。当cur为NULL时根本不会进循环,也就不会出现我们纠结的问题了。 

2.设置random

设置两个指针,cur遍历原链表,copy遍历拷贝链表。

如果cur->random为空,将copy->random也置为空。cur、copy指针后移。

 如果cur->random不为空,令copy->random等于cur->random->next。cur、copy指针后移。

直至cur == NULL循环结束。

3.分离链表 

我们依旧先设置一个哨兵位的头节点,然后使用三个指针:cur记录copy的前一个节点,curnext记录copy的后一个节点,它们用于恢复原链表, copy遍历拷贝链表,将拷贝链表从原链表上分离下来在copyhead链表上进行尾插。同时,为了方便尾插,设置一个tail指针记录copyhead的尾结点。

将copy结点节点尾插到copyhead链表上,tail指针后移,指向新的尾节点,养成好习惯,将tail的next指针置空。cur的next指向curnext节点。(为了避免图太凌乱,我这里省略了拷贝链表上的random指针的线)

重复操作,直至cur为空。 

我们把之前省略的random线补上,这个是最终的效果~当然,和方法一一样,我们返回哨兵位头节点的后一位,也别忘了free哨兵位节点。


三、代码

我们在这两个代码的开头都加上if的判断,如果头指针为空,直接返回空指针。

方法一:

//方法一:
struct Node* copyRandomList(struct Node* head)
{
    if (head == NULL)//链表为空
    {
        return NULL;//直接返回空指针
    }

    //1.复制链表
    struct Node* copyhead = (struct Node*)malloc(sizeof(struct Node));//为哨兵位头节点开辟空间
    struct Node* cur = head;//cur指针遍历原链表
    struct Node* tail = copyhead;//尾指针记录copyhead链表的尾节点,方便尾插

    while (cur)
    {
        struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));//为复制节点开辟空间
        newnode->val = cur->val;//新节点val与cur的val相同
        newnode->next = NULL;//新节点的next指针指向空(一点好习惯)

        tail->next = newnode;//新结点尾插到copyhead链表上

        tail = tail->next;//tail指向新的尾节点
        cur = cur->next;//cur指针后移
    }

    //2.设置random
    cur = head;//cur遍历原链表
    struct Node* curcopy = copyhead->next;//curcopy遍历复制链表(从第一个结点开始)
    struct Node* find1 = head;//find1找寻原链表random
    struct Node* find2 = copyhead->next;//find2确定复制链表random

    for (; cur; cur = cur->next, curcopy = curcopy->next)//当cur不为空,遍历原链表
    {
        if (cur->random == NULL)//当cur的random为空
        {
            curcopy->random = NULL;//将对应的curcopy也置为空
        }
        else//当cur的random不为空
        {
            for (find1 = head, find2 = copyhead->next; find1 != cur->random; find1 = find1->next, find2 = find2->next)//遍历找寻cur的random
            {
                ;
            }
            curcopy->random = find2;//curcopy的random为此时的find2
        }
    }

    //3.返回
    struct Node* first = copyhead->next;//记录第一个节点
    free(copyhead);//释放哨兵位头节点
    return first;//返回第一个结点地址
}

方法二:

//方法二:
struct Node* copyRandomList(struct Node* head)
{
    if(head == NULL)//链表为空
    {
        return NULL;//直接返回空指针
    }

    //1.复制节点
    struct Node* cur = head;//cur指针记录当前节点

    while(cur)
    {
        struct Node* next = cur->next;//next指针记录原链表后一个节点

        struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));//开辟新节点
        newnode->val = cur->val;//令新节点val等于当前节点
        //新结点在cur与next之间插入
        newnode->next = next;//
        cur->next = newnode;
        cur = next;//cur指针后移
    }

    //2.设置random
    cur = head;//cur遍历原链表

    while(cur)
    {
        struct Node* copy = cur->next;//copy遍历拷贝链表

        if(cur->random == NULL)//当前原链表节点random为空
        {
            copy->random = NULL;//将拷贝链表当前节点也置空
        }
        else//当前原链表节点random不为空
        {
            copy->random = cur->random->next;//拷贝链表当前节点的random为原链表当前节点random的后一个
        }

        cur = cur->next->next;//cur在原链表节点上后移一位
    }

    //3.分离链表
    struct Node* copyhead = (struct Node*)malloc(sizeof(struct Node));//开辟哨兵位头结点

    cur = head;//cur遍历原链表
    struct Node* tail = copyhead;//尾指针初始指向copyhead哨兵位的头结点
    
    while(cur)
    {
        struct Node* copy = cur->next;//copy遍历拷贝链表
        struct Node* curnext = copy->next;//curnext记录原链表的下一个方便还原原链表

        tail->next = copy;//取拷贝链表节点尾插
        copy->next = NULL;//尾结点置空
        cur->next = curnext;//还原原链表
        
        cur = curnext;//后移cur指针
        tail = tail->next;//后移tail指针
    }

    struct Node* newhead = copyhead->next;//记录拷贝链表的第一个节点
    free(copyhead);//释放哨兵位头结点
    return newhead;//返回拷贝链表
}

结尾

这题个人认为一个是思想难,方法二着实不容易想到,另一方面需要对链表的插入很熟练,这题可以算得上是链表基本操作的一个总结吧。

最后,若有错误,欢迎大家批评斧正!

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兰兰呀~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值