算法热门:深度拷贝带随机指针的链表(LeetCode 138)

Posted 白龙码~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法热门:深度拷贝带随机指针的链表(LeetCode 138)相关的知识,希望对你有一定的参考价值。

Part I、解读

1、随机指针

对于我们常见的链表,其大多都有一个数据域,和一个指向下一个节点的指针next。今天我们讨论的链表多了一个随机指针random,它的指向可以是链表中的任意一个结点,甚至是NULL。

然而一旦加上了这个随机指针,就会让我们复制链表的任务变得不再那么简单!

2、深度拷贝

既然有深度拷贝,那必然就会有一个对立面:浅度拷贝。二者有什么区别呢?

所谓拷贝,就是复制一个一个对象。

而这里我们要复制的对象就是链表。

对于普通的变量而言,比如:int a = 10,float b = 3.14f……
它们所存储的数据比较单一,都是普通的数值。
而链表与他们不一样的地方在于:链表中存储的不仅有普通的数据,还有指针,而这些指针都指向另一个动态开辟的节点。

如果是浅拷贝,则对于这些动态开辟的节点的地址,我们只是用另外一个指针变量来保存它。而深度拷贝则不然:它会再开辟一块空间,这块空间与原先的那个节点相互独立,但是它们保存的数据是一样的。

换句话说:它们有相同的内涵,但是套上了不同的壳。
我们对深度拷贝的链表进行修改操作不会影响到原来的链表。

Part II、难点分析

对于普通链表的拷贝,我们只需要遍历原链表,然后一个一个地把旧的节点里的东西拷贝到新的结点里就可以了。

但是,一旦链表带有一个随机指针,画风就变了——

我们发现,如果还像之前那样按顺序进行拷贝就会出现一个问题:
这个随机指针很有可能会指向一个在后面的节点,而这个节点还没有来得及拷贝呢!
能不能让这个随机指针还指向原来的那个随机指针指向的节点呢?
不行!因为深度拷贝要求拷贝出来的不能与原对象有任何关联!

Part III、思路分析

既然知道会发生结点来不及开辟的情况,那我们在开始拷贝之前,不管随机指针,先把所有节点都拷贝并链接起来,然后再来单独处理随机指针。于是就变成了这样:
这样做的好处就是:简单,容易想到,但是缺点也很明显——时间复杂度太高。
处理第几个节点的random指针指向哪一个节点的操作是比较复杂的,因为我们需要再一次的遍历链表,找到原节点的random节点与它的相对位置,然后再回到新的节点中从头遍历,找到这个位置,然后再连接起来,比较麻烦!

这里介绍一个非常实用的方法——在每一个节点后面链接一个新结点,这个新节点就是它前面那个节点的拷贝节点。

这样做有什么好处吗?当然!
首先,形成这样的一个链表很容易,只需要创建新结点的同时拷贝复制前一个节点存储的数据,然后让它指向原来的下一个节点就行了,相当于在两个节点之间再插入一个节点。

其次,想要处理随机指针也很容易。我们把原节点记为cur,那么根据上图,cur->next就是复制的新结点。

我们看到,cur->random->next刚好就是我们需要连接的random对象!那么此时只需要:cur->next->random = cur->random->next
然后,random指针就处理好了!(没看明白的可以对照上图看哦)

不过,需要注意的是,如果cur的随机指针指向NULL的话,
cur->random->next就是对空指针解引用了!所以我们在处理random指针的时候需要单独处理这种为空的情况。

最后,当我们把所有的random都处理好之后,需要做的就是把那些新开辟的节点一个一个地取出来连接成新的链表,然后还要恢复原先的链表!

Part IV、代码实现(有详细注释补充上文)

struct Node* copyRandomList(struct Node* head) 
{
    if(head == NULL)//链表为空直接返回即可
        return NULL;
    struct Node* cur = head;
    while(cur)//循环遍历链表,在两个节点之间插入一个新结点
    {
        struct Node* next = cur->next;//存储当前节点的下一个节点
        struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));//开辟新节点
        newnode->val = cur->val;//新开辟的节点拷贝当前节点的值
        cur->next = newnode;//让当前节点的下一个指向新开辟的结点
        newnode->next = next;//新开辟结点的下一个指向原本的下一个节点
        cur = next;
    } 
    cur = head;
    while(cur && cur->next)//处理新结点的random
    {
        if(cur->random)//当random不为空时
            cur->next->random = cur->random->next;//结合图分析即可
        else
            cur->next->random = NULL;
        cur = cur->next->next;
    }
    cur = head;
    struct Node* newhead,*newtail;
    newhead = newtail = cur->next;
    while(cur)//循环遍历,将新结点依次取下来连成新链表,将旧链表恢复
    {
        struct Node* next = newtail->next;
        if(next)
            newtail->next = next->next;
        newtail = newtail->next;
        cur->next = next;
        cur = next;
    }
    return newhead;
}

欢迎大家关注我的博客,定期带来各种学习分享!
如果喜欢的话,给个一键三连哦!!!

以上是关于算法热门:深度拷贝带随机指针的链表(LeetCode 138)的主要内容,如果未能解决你的问题,请参考以下文章

138. 复制带随机指针的链表

数据结构与算法之深入解析复制带随机指针的链表的算法实现

[LeetCode]138复制带随机指针的链表

LeetCode Algorithm 138. 复制带随机指针的链表

LeetCode Algorithm 138. 复制带随机指针的链表

链表OJLeetcode 138. 复制带随机指针的链表(集大成者)