牛客题霸面试真题——链表:JZ35 复杂链表的复制


在这里插入图片描述 在这里插入图片描述

1、原题链接:

https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba

2、题目理解:

(1)要理解什么是深拷贝,什么是浅拷贝?

假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

  • 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存

(2)理解此题链表的结构
在这里插入图片描述
此题的链表的结构体是这样说明的,一个label存值,一个next指针,一个random指针:

struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};

(3)理解什么是链表的深拷贝
将各个节点的值以及其指针都进行深拷贝,就相当于重新new了一个一模一样的链表,同时其指针的指向和原链表相应指向相同。由于相较于其他形式的链表多了random指针,所以拷贝的时候需要也要对random指针赋值。图示:
(4)如何进行链表的深拷贝
通过(3)可知链表的深拷贝不仅要new同样数目的节点,且其next指针、random指针的指向也要重新赋值为原链表对应的位置。

3、解法一:

解法参考自:https://blog.nowcoder.net/n/4c8621e3c72f4956b81813427abe3140

由于我们需要将新链表的next指针、random指针重新赋值为与原链表对应的节点,所以可以记录一个对应关系:假设原节点为x,对应新链表节点xx,用一个map记录原节点-新节点的的映射即m[x,xx],那么新节点的random指针指向xx->random=m[x->random]=m[y](这里用了两次映射,其一用 [x,xx] 找到key=x节点的random指针:x->random=y,其二用 [y, value] 找到 value=xx->random)。
请添加图片描述
第一次遍历原链表,创建与原链表等价的节点,并按序将新节点进行连接,同时记录原链表节点到新节点的映射表m;第二次遍历映射表,通过映射表对新节点的random指针的指向赋值。
代码:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        //新链表头节点
        RandomListNode *newHead = new RandomListNode(-1);
        //cur遍历原链表,newcur遍历新链表
        RandomListNode *cur=pHead,*newcur=newHead;
        //创建映射表m
        unordered_map<RandomListNode*, RandomListNode*> m;
        while(cur){
            //创建新节点,并连接
            RandomListNode *clone = new RandomListNode(cur->label); 
            newcur->next=clone;
            //记录映射表m
            m[cur]=clone;
            newcur=newcur->next;
            cur=cur->next;
        }
        newcur=newHead->next;
        //遍历映射表m
        for(auto [key,value]:m){
            value->random = (key->random==nullptr)? nullptr : m[key->random];
        }
        return newHead->next;
    }
};

时间复杂度:O(n), 遍历一次链表和哈希表的时间
空间复杂度:O(n), 哈希表使用的空间

关于unordered_map的使用请查看其他博主的文章:https://blog.csdn.net/zou_albert/article/details/106983268

4、解法二:

解法参考自:https://blog.nowcoder.net/n/4c8621e3c72f4956b81813427abe3140

由于我们需要将新链表的next指针、random指针重新赋值为与原链表对应的节点。所以可以按照下面的方法:在第一次遍历原链表时创建新节点,将其插入在原结点后面;第二次遍历链表时对random指针赋值;第三次遍历遍历链表时将新节点拆出来。
在这里插入图片描述
代码:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        if (!pHead) return nullptr;
        RandomListNode *cur = pHead;
        while(cur){
            //创建新节点,并插入
            RandomListNode *clone = new RandomListNode(cur->label);
            clone->next=cur->next;
            cur->next=clone;
            //更新cur
            cur=clone->next;
        }
        cur=pHead;
        //新链表头节点
        RandomListNode *newHead = pHead->next;
        RandomListNode *newcur = newHead;
        while(cur){
            //对新节点的random指针赋值
            newcur->random=(cur->random==nullptr)?nullptr:cur->random->next;
            //先更新cur再更新newcur,newcur不为null才有newcur->next
            if(newcur) cur=newcur->next;
            if(cur) newcur=cur->next;
        }
        //拆分链表
        cur=pHead,newcur=newHead;
        while(cur){
            //交替连接,并后移指针
            if(newcur) cur->next=newcur->next;
            cur=cur->next;
            if(cur) newcur->next= cur->next;
            newcur=newcur->next;
        }
        return newHead;
    }
};

一定要注意特判,如果没有

if (!pHead) return nullptr;

会出现以下的错误
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值