题目描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
示例:
题目理解:
题目描述中的拷贝链表结点,提到了深拷贝,有深拷贝,那么就一定有浅拷贝,我们先来区分一下深拷贝和浅拷贝的区别。
-
浅拷贝:
浅拷贝只是复制指针的值,也就是只复制了对象的地址,而不会复制指针所指向的实际数据。这就导致原对象和拷贝对象会共享同一块内存空间,对其中一个对象的修改会影响到另一个对象。 -
深拷贝:
深拷贝不仅会复制指针的值,还会为拷贝对象分配新的内存空间,并将原对象的数据复制到新的内存空间中。这样,原对象和拷贝对象就拥有各自独立的内存空间,对其中一个对象的修改不会影响到另一个对象。
题目要求为深拷贝,那么在复制时就需要申请新的结点用来保存被复制结点的值。
题解:
题目示例中,每个结点由三个元素构成,分别为val,*next,*random。我们复制普通链表时,可以很容易的通过next指针找到后续结点,并进行复制,但此处random指针并不好处理。
所以我们采用一个巧妙的方法——申请节点后在被复制节点处进行后插。
在后插结束后,让pcur指针指向头结点,copy指针指第二个结点。
此时我们就会发现,利用这两个指针就可以更新新复制结点的random指针:
即:copy->random = pcur->random->next
在修改完所有复制节点random指针后,我们再将新复制结点从整个链表中抽离出来,就完成了原链表的深拷贝。
具体代码如下:
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
typedef struct Node Node;
//1.创建新的结点,用于保存需要复制结点的val值,并将next和random指针置为空
Node* buynode(int x)
{
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = x;
newnode->next = newnode->random = NULL;
return newnode;
}
//2.将新建立的结点插入在被复制的结点之后
void nodeinsert(Node* head)
{
Node* pcur = head;
while(pcur)
{
Node* pnext = pcur->next;
Node* newnode = buynode(pcur->val);
newnode->next = pcur->next;
pcur->next = newnode;
pcur = pnext;
}
}
//3.修改已经创建好的结点的random指针,若pcur的random指针本身为空,则不需要更改
void setrandom(Node* head)
{
Node* pcur = head;
while(pcur)
{
Node* copy = pcur->next;
if(pcur->random != NULL)
copy->random = pcur->random->next;
pcur = copy->next;
}
}
struct Node* copyRandomList(struct Node* head) {
//针对链表为空的情况特殊处理,若链表为空,直接返回空
if(head == NULL)
return NULL;
//1.申请一个新的结点复制完成后插入在被复制的结点之后
nodeinsert(head);
//2.修改random指针的指向
setrandom(head);
//3.将复制的链表从原链表中抽离出来
Node* pcur = head;
Node*newhead,*ptail;
newhead = ptail = pcur->next;
while(ptail->next)
{
pcur = ptail->next;
ptail->next = pcur->next;
ptail = pcur->next;
}
//返回的newhead即为深拷贝链表
return newhead;
}