例题描述
给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空节点。
要求返回这个链表的深拷贝。
深度拷贝为拷贝出的新对象在堆中
重新
分配一块内存区域。所以对新对象的操作不会影响原始对象。
示例:
-
输入:
{"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1}
-
解释:
节点1
的值是1
,它的下一个指针和随机指针都指向节点2
。
节点2
的值是2
,它的下一个指针指向NULL
,随机指针指向它自己。
不宜看懂?
请看下图:
题意为:给定一链表,链表中:
- 第
1
个结点的random
指针指向第3
个结点 - 第
2
个结点的random
指针指向第1
个结点 - 第
3
个结点的random
指针结点自身 - 第
4
个结点的random
指针指向NULL
现在需要返回此链表的深拷贝,也就是复制一份链表,这份新链表中的所有结点都有自己独立的内存空间,比如新链表的1
号结点指向新链表自己的3
号结点,而不是原链表的3
号结点。
结点结构体定义
struct RandomListNode {
int label;
struct RandomListNode* next;
struct RandomListNode* random;
};
我们可以看到,他的定义相比于普通链表的定义多了一个random
随机指针。
解题思路
需要返回链表的深拷贝,不能指向原链表的结点。那么是否有什么办法可以使新老两链表产生一定映射关系,建立联系,通过索引老结点来找到新结点。
这里介绍一种思路:
因为是单链表,除随机指针外只有next
可以利用。将旧连表中各个结点逐个拷贝后,将每个结点安插在旧结点的next
结点。旧连表指针只遍历旧结点,新链表指针只遍历新结点,然后将两链表拆分,返回新链表即可。是不是十分微妙呢~
- 因为要遍历链表所有结点,需要多次发生创建结点的行为,所以我们选择将它封装成为一个函数,进行调用。
struct RandomListNode *CreateNode(int label) {
struct RandomListNode *p = (struct RandomListNode*)malloc(sizeof(struct RandomListNode));
p->label = label;
p->next = p->random = NULL;
return p;
}
- 判断极端情况,如果链表为空,那么无法无法拷贝、或者说拷贝之后仍是一个空链表,直接返回
NULL
。 - 创建新结点,逐个申请空间,赋值原链表中每个结点。
- 通过更改指向,让新结点跟在老结点的后面。
(红箭头表示老结点向新结点的指向,蓝箭头表示新结点向老结点的指向)
- 拆分新老链表,使它们各自成为一个独立的个体,最终在没有改动老链表的前提下,返回新链表接口。
至此,链表的深拷贝逻辑返回成功。
代码实现
struct RandomListNode* copyRandomList(struct RandomListNode *head) {
//判断极端情况,链表为空
if (head == NULL) {
return NULL;
}
//1. 复制原链表,让新的结点跟在老的结点后边
struct RandomListNode *oldNode = head;
while (oldNode != NULL) {
struct RandomListNode *newNode = CreateNode(oldNode->label); //调用创建结点函数
newNode->next = oldNode->next; //把老链表当前结点的下一结点接在新结点之后
oldNode->next = newNode; //把新链表结点接在老链表结点之后
oldNode = oldNode->next->next; //此时老结点一次走两步才能指向老链表中的结点
}
//2. 处理random指针
oldNode = head;
while (oldNode != NULL) {
struct RandomListNode *newNode = oldNode->next;
if (oldNode->random != NULL) {
newNode->random = oldNode->random->next;
//相当于oldNode->next->random = oldNode->random->next
}
oldNode = oldNode->next->next;
}
//3. 把一个链表拆分成两个链表
oldNode = head;
struct RandomListNode *newHead = head->next; //保存新链表的头指针
while (oldNode != NULL) {
struct RandomListNode *newNode = oldNode->next;
oldNode->next = newNode->next;
if (oldNode->next != NULL) {
newNode->next = oldNode->next->next;
//形如 1->3 2->4
}
else {
newNode->next = NULL; //其实没必要特殊处理,因为它本身就是空指针
}
//oldNode->next 已经更新,所以往后走一个next单位即为原来老链表的两个单位
oldNode = oldNode->next;
}
return newHead;
}