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;
会出现以下的错误