力扣LeetCode138. 复制带随机指针的链表 两种解法(C语言实现)

目录

题目链接

题目分析

 题目定位:

解题思路

解题思路1(粗暴但是复杂度高)

 解题思路2(巧妙并且复杂度低)


题目链接

138. 复制带随机指针的链表icon-default.png?t=N7T8https://leetcode-cn.com/problems/copy-list-with-random-pointer/

题目分析

 题目定位:

本题属于链表中较为综合的题目,考验做题者的思想以及用代码实现的能力,能够真正理解并做出此题需要对链表有相对熟练的掌握度。


话不多说,先来分析题目:

题目是想让我们构造一个链表的拷贝,也就是开辟一块一模一样的链表,但是本题中的链表,又和普通的单链表不一样,如图:

我们发现,链表中每一个节点中的结构体成员除了valnext之外,还有一个random,而random是指向链表中的任意一个节点,甚至可以是NULL。

因此要拷贝这个链表的难度就在于,如何使拷贝的链表中每一个节点的random指向其对应的节点呢?

解题思路

解题思路1(粗暴但是复杂度高)

第1步.先忽略原链表的random指针,对其进行复制,如图:

(1)定义两个struct Node*类型的指针copyhead和copytail来储存复制链表的头和尾,并初始化为NULL;并且定义一个struct Node*类型的指针cur指向head


(2)若原链表不为空,则此时cur指向第一个节点。用malloc开辟一块空间作为复制的第一个节点,并定义一个copy指针来保存,并将copy->val改为cur->val,并让copyhead和copytail指向copy节点


(3)让cur移向下一个节点。用malloc开辟一块空间作为复制的第二个节点,并让copy指向它,并将copy->val改为cur->val,先让copytail->next指向copy来连接两个节点,再让copytail指向copy节点(更新尾节点)。

像这样一直循环下去直到cur指向NULL,循环结束,最后再让copytail->next = NULL,此时便完成了第一步的复制操作

此时我们便可以根据图解来写代码

struct Node* copyRandomList(struct Node* head) {//1.忽略random复制链表struct Node* cur = head;struct Node* copyhead = NULL;struct Node* copytail = NULL;while(cur){struct Node* copy = (struct Node*)malloc(sizeof(struct Node));copy->val = cur->val;if(copyhead == NULL){copyhead = copy;copytail = copy;}else{//先连接copytail->next = copy;//再指向copycopytail = copy;}cur = cur->next;}copytail->next = NULL;
}

第2步.处理拷贝链表的random指针

(1)查找原链表中的random指向的节点到底是第几个节点,并定义一个变量pos来记录下来

①如果原链表random指向NULL,则不需要找pos

②如果原链表的random指向链表中的节点,每次循环开始可以初始化一个新的指针newcur=head,再对newcur进行迭代,每当newcur向后走一次,pos加一,直到newcur == cur->random的时候,循环结束,举一个例子:


 (2)用一个循环来使拷贝链表中random指向其对应的节点

①如果原链表的random指向NULL,那么便很简单:拷贝的random直接置为NULL;

②如果原链表的random指向链表中的节点,每次循环开始可以初始化一个新的指针newcopy=copyhead,那么拷贝的random则可以通过一个循环来找到pos所对应的第几个节点,接着上面的例子:

根据图解我们可以写出代码:

struct Node* copyRandomList(struct Node* head) {//1.忽略random复制链表struct Node* cur = head;struct Node* copyhead = NULL;struct Node* copytail = NULL;while (cur){struct Node* copy = (struct Node*)malloc(sizeof(struct Node));copy->val = cur->val;if (copyhead == NULL){copyhead = copy;copytail = copy;}else{//先连接copytail->next = copy;//再指向copycopytail = copy;}cur = cur->next;copytail->next = NULL;}//2.处理拷贝链表的random指针cur = head;struct Node* copy = copyhead;while (cur){int pos = 0;if (cur->random == NULL){copy->random = NULL;}//找到cur的random指向对应的第几个节点else{//让newcur和newcopy重新指向第一个节点struct Node* newcur = head;struct Node* newcopy = copyhead;while (newcur != cur->random){newcur = newcur->next;pos++;}//使copy的random指向对应的第pos个节点while (pos--){newcopy = newcopy->next;}copy->random = newcopy;}//cur和copy向后走cur = cur->next;copy = copy->next;}return copyhead;
}

 题解一总结:优点是这种思路比较容易想到并且理解;缺点是由于找到原链表找到random指向的节点的pos的过程,每个节点的复杂度为O(n),又有n个节点,因此这种解法的时间复杂度为O(n²),并不是一个优秀的解法,接下来将为大家讲另一种优秀的解法。


 解题思路2(巧妙并且复杂度低)

第1步 把拷贝节点连接再原节点的后面

用这种方式处理是为了方便找到拷贝链表的random应该指向第几个节点,random指向的这个节点的下一个节点就是拷贝链表要找的random节点

根据图解我们可以写出代码:

//1.链接链表struct Node* cur = head;while(cur){//提前储存原链表下一个节点的地址struct Node* next = cur->next;//为原链表的拷贝开辟空间struct Node* copy = (struct Node*)malloc(sizeof(struct Node));copy->val = cur->val;//链接原链表和拷贝节点cur->next = copy;copy->next = next;//迭代往下走cur = next;}

 第2步 拷贝原链表的random节点

 (1)若原链表的节点的random指向NULL,拷贝节点的random也指向NULL


(2)若原链表的节点random指向原链表的其中一个节点,那么random指向的这个节点的下一个节点就是拷贝链表要找的random,以原链表二个节点为例:

(3)实现后题解如下

根据图解我们可以写出代码:

//2.拷贝原链表的random节点cur = head;while(cur){struct Node* copy = cur->next;if(cur->random == NULL){copy->random = NULL;}else{copy->random = cur->random->next;}cur = copy->next;}

第3步 将拷贝节点与原链表分开

(1)用cur和copy指针来分别使原链表和拷贝链表的节点向后迭代,由于copy随着cur向后迭代,一直跟在cur的后面

因此循环外初始化cur = head,循环内初始化copy = cur->next;

 (2)为原链表定义一个next来连接链表;为拷贝链表定义一个copyhead和copytail来返回和连接链表,当原链表第一个节点不为空,才能保证copy的第一个节点不为空,因此copyhead和copytail先置空

循环外初始化copytail = copyhead = NULL,循环内初始化next = copy->next, 

(3)当cur指向第一个节点时,进入循环,此时copyhead和copytail为空,使其指向拷贝链表的第一个节点,再使cur->next = next,将cur和next连接起来,并使cur=next往后走,

 若cur不为空,再之后就是让copy=cur->next,next = copy->next

  捋清楚结构就可以写出循环体框架:

    cur = head;struct Node* copyhead = NULL;struct Node* copytail = NULL;while(cur){struct Node* copy = cur->next;struct Node* next = copy->next;if(copyhead == NULL){copyhead = copytail = copy;}cur->next = next;cur = next;}

(4)当cur指向第二个节点时,copyhead和copytail此时不为空,copy也指向拷贝的第二个节点,因此用copytail->next = copy连接两节点,再使copytail = copy使其向后迭代

捋清关系我们便可以补全循环体

//3.将拷贝节点与原链表分开cur = head;struct Node* copyhead = NULL;struct Node* copytail = NULL;while(cur){struct Node* copy = cur->next;struct Node* next = copy->next;if(copyhead == NULL){copyhead = copytail = copy;}else{copytail->next = copy;copytail = copy;}cur->next = next;cur = next;}

完整代码: 

struct Node* copyRandomList(struct Node* head) {//1.链接链表struct Node* cur = head;while(cur){//提前储存原链表下一个节点的地址struct Node* next = cur->next;//为原链表的拷贝开辟空间struct Node* copy = (struct Node*)malloc(sizeof(struct Node));copy->val = cur->val;//链接原链表和拷贝节点cur->next = copy;copy->next = next;//迭代往下走cur = next;}//2.拷贝原链表的random节点cur = head;while(cur){struct Node* copy = cur->next;if(cur->random == NULL){copy->random = NULL;}else{copy->random = cur->random->next;}cur = copy->next;}//3.将拷贝节点与原链表分开cur = head;struct Node* copyhead = NULL;struct Node* copytail = NULL;while(cur){struct Node* copy = cur->next;struct Node* next = copy->next;if(copyhead == NULL){copyhead = copytail = copy;}else{copytail->next = copy;copytail = copy;}cur->next = next;cur = next;}return copyhead;
}

 题解二总结:由于第二种解题思路用巧妙的连接方式,使拷贝链表的random不需要通过对原链表的每一个节点遍历也能找到,大大降低了时间复杂度,这种解法的时间复杂度为O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/621067.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot的旅游管理系统+论文+ppt+免费远程调试

项目介绍: 基于SpringBoot旅游网站 旅游管理系统 本旅游管理系统采用的数据库是Mysql,使用SpringBoot框架开发。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。 (1&…

QQ 邮箱使用 SMTP 发送邮件报错:550 The From header is missing or invalid

文章目录 场景描述问题排查根据提示查看原因查看封装的 message 个人简介 场景描述 QQ 邮箱使用 SMTP 发送邮件报错:550 The From header is missing or invalid: 失败原因:(550, bThe "From" header is missing or invalid. Ple…

AI大模型探索之路-应用篇12:AI大模型应用之向量数据库选型

目录 前言 一、什么是向量数据库? 二、向量数据库的应用场景 1. 图像检索 2. 推荐系统 3. 自然语言处理 三、向量数据库在AI大模型中的应用 1. 训练数据的索引和检索 2. 特征存储和管理 3. 模型中间结果的存储 4. 长上下文的记录和检索 5. 本地知识库的构…

9. Spring Boot 日志文件

本篇文章源码位置延续上个章节:SpringBoot_demo 本篇文章内容源码位于上述地址的com/chenshu/springboot_demo/logging包下 1. 日志的作用 发现和定位问题: 日志是程序的重要组成部分,它在系统、程序出现错误或异常时提供诊断和解决问题的线…

【热门话题】PyTorch:深度学习领域的强大工具

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 PyTorch:深度学习领域的强大工具一、PyTorch概述二、PyTorch核心特性…

Linux:zabbix配置网易邮箱告警(5)

1.开启邮箱的smtp服务 这里我使用网易邮箱,我需要先去开启邮箱的POP3/IMAP/SMTP/Exchange/CardDAV 服务 点击POP3/SMTP/IMAP 然后在这里开启两个服务 我这个已经去扫码添加过了,你点击新加授权码再去扫码发送信息,就可以得到一个授权密码 2.…

Vue2 基础学习-案例实践

数据管理信息的增删改查的实践 主要应用&#xff1a; 数据插值&#xff1a; {{xxx}}双向绑定&#xff1a;v-model点击事件函数&#xff1a;click列表xxx的增删改实现 xxx.push(row) 增加xxx.splice(id,1) 删除 一行{x,y} xxx[id]; 编辑 <!DOCTYPE html> <html la…

关于AG32 MCU的一些奇思妙想

1、AG32VF103的网口是100M还是10M&#xff1f; RE: 都是100M的。 2、用FPGA能不能再仿出一个网口&#xff1f;有些产品用到两个网口。 理论上可以&#xff0c;但是要考虑&#xff0c;一个是cpld实现难度&#xff0c;一个是需要的逻辑单元。因为mac逻辑多&#xff0c;内置的2KL…

JavaEE企业开发新技术5

目录 2.18 综合应用-1 2.19 综合应用-2 2.20 综合应用-3 2.21 综合应用-4 2.22 综合应用-5 Synchronized &#xff1a; 2.18 综合应用-1 反射的高级应用 DAO开发中&#xff0c;实体类对应DAO的实现类中有很多方法的代码具有高度相似性&#xff0c;为了提供代码的复用性,降低…

链表基础3——单链表的逆置

链表的定义 #include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node* next; } Node; Node* createNode(int data) { Node* newNode (Node*)malloc(sizeof(Node)); if (!newNode) { return NULL; } newNode->data …

(弟弟14)递归•按顺序打印一个整数的每一位

这里是目录哦 题目代码运行截图递归思路递归停止条件如何实现“按顺序”悟了✨加油&#x1f389; 题目 按顺序打印一个整数的每一位。 代码 #include<stdio.h> void Print(int n) {if (n > 9)//递归停止条件{Print(n / 10);//不断趋近递归停止条件}printf("%d…

游戏实践:扫雷

一.游戏介绍 虽然很多人玩过这个游戏&#xff0c;但还是介绍一下。在下面的格子里&#xff0c;埋的有10颗雷&#xff0c;我们通过鼠标点击的方式&#xff0c;点出你认为不是雷的地方&#xff0c;等到把所有没有雷的格子点完之后&#xff0c;及视为游戏胜利。 上面的数字的意思…