【图文解析】复制带随机指针的链表(返回链表的深拷贝)


例题描述

给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空节点。

要求返回这个链表的深拷贝

深度拷贝为拷贝出的新对象在堆中重新分配一块内存区域。所以对新对象的操作不会影响原始对象。

在这里插入图片描述

示例:

  • 输入:
    {"$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;
}
  1. 判断极端情况,如果链表为空,那么无法无法拷贝、或者说拷贝之后仍是一个空链表,直接返回NULL
  2. 创建新结点,逐个申请空间,赋值原链表中每个结点。
    在这里插入图片描述
  3. 通过更改指向,让新结点跟在老结点的后面。
    (红箭头表示老结点向新结点的指向,蓝箭头表示新结点向老结点的指向)
    在这里插入图片描述
  4. 拆分新老链表,使它们各自成为一个独立的个体,最终在没有改动老链表的前提下,返回新链表接口。
    在这里插入图片描述
    至此,链表的深拷贝逻辑返回成功。

代码实现

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;
}
  • 2
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:像素格子 设计师:CSDN官方博客 返回首页
评论

打赏作者

giturtle

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值