数据结构——链表OJ题目讲解(2)

news2024/11/29 10:58:32

作者:几冬雪来

时间:2023年3月10日

内容:数据结构链表OJ题目讲解

来源:牛客网和力扣

目录

前言: 

 刷题:

1.反转链表:

1.改变指向的解法:

2.取头结点插入到新链表: 

2.合并两个有序链表: 

1.正常尾插:

 2.带哨兵位尾插:

 3.链表分割:

4.链表的回文结构: 

5.相交链表:

结尾:


前言: 

在上一篇博客中我们讲解了一些有关链表的OJ题目,但是在刷题的过程中我们的题目并没有覆盖到方方面面,有一些链表拓展出来的新的写法我们没有讲解到。因此今天我们又选取了几道题目来进行我们的讲解。 

 刷题:

刷题是我们在学习数据结构又或者是学习语言等必不可少的过程,我们通过刷题可以了解到一些不同的解题思路和方法。也可以发现不少我们写题的时候可能会犯错的地方,因此刷题是一种可以提升我们解题能力的一种手段。 

1.反转链表:

反转链表作为数据结构链表的一道经典题目,如今还在许多公司等作为面试的题目使用。 

1.改变指向的解法:

这道题用指针的解法来解,因为要调换前后两个结点的指向,因此单指针绝对是不行的,起码都要是双指针。但是这道题仅仅是双指针还是不够的,假设我们让第二个结点指向第一个结点,这样两者的指向就交换了,但是这个时候第二个结点不再指向第三个结点,我们就会找不到第三个结点了。因此,在这里我们还需要创建一个指针用来保存我们的第三个结点

那我们这个程序什么时候结束呢?是n3为空的时候吗?并不是,这个程序我们结束的标志是n2为空的时候才结束。 

这里一开始先创建三个指针,指向就是上面画的那种图,n1指向空,n2指向头结点,n3指向n2的下一个结点。然后开始循环,因为n2为空我们的编程结束,所以循环条件也就为n2。接下来就是翻转,翻转前是n1->next = n2那么将其翻转后就是n2的下一个结点指向n1,也就是n2->next = n1。接下来n1,n2,n3全部向后移动一个位置,然后再次循环,最后返回n1指针。

运行代码后,代码出现了错误,根据报错提醒我们的n3 = n3->next会出现空指针的问题,因为代码是n2为空的时候结束,当n2指向尾结点的时候,n3就已经为空了。这个时候n3 = n3->next会出错就很正常。因此,在这里我们要给n3的执行加上一个条件。

如果n3已经为空的话,我们的n3 = n3->next这个代码就不用执行了。这样就能保证n3不会出现空指针的问题。再次运行代码。

当我们的链表为空的时候,这个代码又出错了,如果链表为空的话,我们这里就没有头结点。下面的赋值多多少少都会出错,因此在一开始我们就要对它是否为空进行判断

这样我们的代码就通过了。那么下来我们就将代码放上来。

当然这个问题我们两种方法来解决。 

2.取头结点插入到新链表: 

对于这道题我们的另一种解决方法就是取头结点,然后将其插入到新链表。这种方法我们在学习数据结构链表的时候也经常用到,这里我们就画图说明一下即可。

在这里我们需要创建两个指针,一个指针用于插入另一个指针用于保存数组,直到原链表的所有结点都插入完毕即可。 

开始讲我们给原链表的头文件定义一个指针cur,而后定义一个空指针作为新链表。接下来就是循环,因为要结点插入,有多少个结点就插入多少个,这里的循环条件就为cur。然后进入循环,先创建一个指针保存cur->next,而后开始头插,将cur->next指向空作为我们的新链表的尾结点接下来newhead要进行更新,cur也指向下一个结点。 

运行代码起来看看。

  

用例都通过,这就证明我们的代码和思路没有问题,同时相比较上面那种方法,取头结点插入新链表的代码量明显少了。

2.合并两个有序链表: 

 下来就是一道新的题目了,题目要求我们合并合并两个有序链表,形成一个新的升序的链表,然后将其返回。而我们要做的就是将两个链表的结点依次比较,取小的值进行尾插

1.正常尾插:

这个就是我们的操作原理,就是两个链表之间的结点进行相比较,最终形成一个新的链表。下来就来看看我们的代码是怎么样的。 

 在这个代码一开始创建两个指针指向两个链表的头结点,同时再创建两个指针作为新的链表的头结点。接下来进入循环,无论哪一个指针先走我们就结束循环。然后就是判断,如果cur1的值小于cur2的话,我们就进行if语句

首先对新链表是否为空进行判断,如果head为空的话,这里进行的操作就不是插入而是赋值操作如果不为空则将cur1的值给tail->next,然后tail结点进行更新,向后走一步,同时cur1也向后走一步来到它的下一个结点。如果cur2大于cur1,则也是如果操作,只不过将里面的cur1更换为cur2罢了

注:因为我们cur1并没有被修改,因此不需要一个新指针保存下一个结点。

最后如果哪个链表先走完,那就将另一个链表直接并在新链表的后面,因为我们这里并没有修改原链表的值,因此只要将一个结点链接到我们新链表的后面就可以了,后面的结点本身就是链接到一起的

但是我们的这个代码是存在问题的,也就是空指针的问题,看用例的话则是第一个指针为空的情况。如果链表为空,那么我们的赋值操作就不会进行。 

 因此我们要在一开始对其进行判断,是否为空的情况。

如果list1为空我们就返回list2,反之返回list1。 

 

修改过后我们的代码也是成功的通过了所有的用例。那么还是老规矩,运行成功之后我们要将代码给放上来。 

 2.带哨兵位尾插:

既然上面那种方法已经了解了,那么接下来我们要介绍一种新方法——带哨兵位的链表操作。 

在我们上边的代码中,书写的时候要注意很多空指针的问题。例如两个链表一开始是否为空,第一次插入新链表的时候要判断是否为空来决定是赋值操作还是插入操作

同时哨兵位也不存放我们的有效值。

但是这里采用哨兵位的解法就没有这些问题了,那么哨兵位是怎么样操作的呢?

类似这种方法,一开始我们就创建一块空间,使我们新创建的两个指针指向这块空间,因为两个指针都存在有空间所以它们不可能为0。那么接下来我们应该怎么对我们的代码进行修改呢?

这里我们就创建两个指针,并让指针指向一块空间。然后将tail->next置空,为链表的插入做准备。这里因为tail有指向一块空间不为空,因此判断结束准备插入的时候不用再对tail是否为空进行判断。下面的进程是一样的,最后因为链表是没有哨兵位的,所以结束时要将创建的那块空间进行一个释放。最后返回我们新链表的头结点的位置。

最后结果也可以看出来我们的思路并没有错误。

 3.链表分割:

这道题是我们从牛客网中找到的题目,链表分割在牛客网给出的难度是困难,但是在力扣里面难度可能会变为中等,不过即使这样我们依旧要解决这道题。 

这道题我们依旧要创建哨兵位来求解,这样子会更加方便。然后进行尾插操作,小于x的尾插到一个链表中,大于等于x的分装到另一个链表上去,最后将两个链表相链接

假设我们输入的为1  5  2  3  7  4

多说无益,接下来我们就通过写代码来证明。 

这里我们先创建四个指针,两个固定指针,两个移动指针。然后创建两个哨兵位的头结点给两个固定的指针。接下来把两个移动指针的下一个位置置空用于链表链接使用。紧接着创建一个指针,指向原链表的头结点,下来开始循环如果cur->next小于x,那么我们就将其插入到小于x的链表中反之插入到大于等于的链表中,又因为我们插入了新结点,所以我们的lTal或者gTail需要更新往下走一步,同时cur也向下走一步。最后将两个链表链接在一起,并将哨兵位删除释放

多少运行起来我们的代码就出现了问题,这个代码的问题就是发生了内存超限。为什么会内存超限?明明没有用到多少内存,这里就出现了链表中一个经典的问题——环问题。 

 这里可以看见我们的链表链接后,最后一个结点指向的并不是空而是3,这是因为我们这里插入方法采取的是尾插,不改变本次插入的next指向,只是改变上一个结点的next指向。

因此在最后将其链接后最后一个结点的next置空即可。

 

这里不知道为什么没有通过,我也不知道哪里错了。但是思路应该是没问题的

4.链表的回文结构: 

 那么什么是链表的回文呢?简单来说就是链表的对称。

那么我们该怎么解决这道题呢?这道题也有一种很简单的方法。

1.找到中间结点

2.从中间结点开始,对后半段进行逆置

3.前半段和后半段进行比较

这就是我们这道题的解决思路。这里有人就要问了:偶数逆置看得懂,但是奇数的链表逆置后变为了1 2 1 2 3是不一样的,怎么可能成立,但是其实这个地方即使是逆置了,前一个2的next的指向依旧指向3,并没有被修改。 

那么我们该怎么书写我们的代码呢?这里我们可以偷个懒,从我们往期博客中将反转链表和链表的中间结点两道题目的代码复制过来使用

一开始我们就使用反转链表和链表的中间结点题目的代码,分别求出中间结点,并且将中间结点后面的所有结点进行逆置操作,并将中间结点的位置设计为一个新的头结点。然后进行判断,在head&&rhead中如果二者中有一个不相等我们就返回false,相反如果相同的话我们的两个头结点就往后走一步。如果循环结束了我们的head依旧等于rhead,这个时候返回true就可以了。

最后通过测试也是可以看出我们的代码并没有什么问题。那么我们这道题目也就成功解决了,接下来就是另一道题目了。

5.相交链表:

在数学中我们经常遇到函数相交的情况。

两条函数相交与一个点,那么我们可以说这两条函数相交,这个相交的地方也被我们称为交点。那么我们的相交链表也是这样的吗?如果这样想那就大错特错了。 

这种理解是错误的,因为当我们链表相交之后,它们会交于一个共同的结点,而这个结点只有一个next,它只会指向下一个结点,并不会指向两个不同的结点。 

那我们要怎么判定两个链表相交了呢?

这里我们通过判定尾结点的地址来看看两个链表有没有相交。如果两个链表的尾结点的地址相同,那么它们就是相交的,如果不同那就不相交

注:判断的是尾结点的地址而不是尾结点的值。

但是如果这个样子,那么我们要怎么样找到交点?有人说找到a2和b3的next即可,但是这里我们又怎么确定a2和b3在哪里? 那么这里最简单粗暴的就是直接暴力比较。拿a1与b1~c3相比较,如果都不相等,那么就拿a2和b1~c3再次相比。但是如果是这种方法的话,我们的时间复杂度就为O(N^2),它就是一个很大的值了。

如果题目有给我们一个限制,要求我们的时间复杂度为O(N)空间复杂度为1,那这道题我们又应该怎么办解。

那说到这里,我们就动手写代码吧。

 这里一开始我们就创建两个指针来存放两个链表的头结点,并定义两个变量赋值为1。然后就是循环判断,先找两个链表的尾结点并且lenA和lenB都要移动,为了后面比较长短做准备,如果尾结点不同我们就直接返回空。如果相等的话,我们就创建一个变量来存放差距步的多少,因为这里不知道两个链表哪个比较长,因此在这之后还要再创建两个指针指向我们的头结点,一开始我们就直接先假设A链表比B链表长,然后判断,要是链表A没有链表B长,我们就将两个值调换

接下来我们的长链表就要先走差距步,gap就是差距步的个数。当差距步走完后我们就开始判断,如果longList不等于shortList,说明二者并没有指向同一个地址,这里的两个结点就要同时向后走一步,如果找到了就直接返回指针的地址

最后我们这个代码也是通过了用例。 

结尾:

本次我们链表OJ的题目也就到这里结束了,在这篇博客中又学习到了不少新知识——哨兵位的头结点,但是还是有一些链表的题目我们并没有讲到,例如后面的链表环形结构等。不过这些题目也并不简单,后面要熟悉链表的得认真反复的去学习。最后希望这一篇链表OJ题目的讲解可以为各位带来帮助,让大家更了解链表知识。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

参考 | 辨别真假笔记本三星内存条 (ddr4)

参考 | 辨别真假笔记本三星内存条 (ddr4) 文章目录参考 | 辨别真假笔记本三星内存条 (ddr4)1. 三星内存条标签纸上编码的含义2. 三星内存颗粒上编码的含义3. 辨别内容参考1. 三星内存条标签纸上编码的含义 内存条贴张上面有两串值得注意的编码, 其中编码的具体意义参考三星官方…

docker-compress 配置

文章目录docker-compress下载安装常用命令Docker Compose配置常用字段docker compose案例yml 配置指令参考versionbuildcap_add,cap_dropcgroup_parentcommandcontainer_namedepends_ondeploydevicesdnsdns_searchentrypointenv_fileenvironmentexposeextra_hostshe…

【3.10】操作系统进程管理、KMP算法

多线程冲突了怎么办? 由于多线程执行操作共享变量可能会导致竞争状态,因此我们将此段代码称为临界区(*critical section*),它是访问共享资源的代码片段,一定不能给多线程同时执行。 我们希望这段代码是互斥…

cadence skill 记录FPM不能保存问题

;FPM skill by Richard L. version0.08 fpmontrealgmail.com;Tree:杂项(Chinese)/简单范例;Desc:范例如何建立一个简单的二极管封装;Vendor:Richard L.;Count:1;CVG64:示意图字段(测试中);Datasheet:pL12.7 ;引脚间距pA7.6 pB3.5 pH3.5 ;长宽高pPad2.0 pHole1.2 ;焊盘直径和孔径…

论文阅读《Block-NeRF: Scalable Large Scene Neural View Synthesis》

论文地址:https://arxiv.org/pdf/2202.05263.pdf 复现源码:https://github.com/dvlab-research/BlockNeRFPytorch 概述 Block-NeRF是一种能够表示大规模环境的神经辐射场(Neural Radiance Fields)的变体,将 NeRF 扩展到…

渗透测试——信息收集(详细)

信息收集:前言:信息收集是渗透测试除了授权之外的第一步,也是关键的一步,尽量多的收集目标的信息会给后续的渗透事半功倍。收集信息的思路有很多,例如:页面信息收集、域名信息收集、敏感信息收集、子域名收…

Redis学习【12】之Redis 缓存

文章目录前言一 Jedis 简介二 使用 Jedis2.1 测试代码2.2 使用 JedisPool2.3 使用 JedisPooled2.4 连接 Sentinel 高可用集群2.5 连接分布式系统2.6 操作事务三 Spring Boot整合Redis3.1 创建工程3.2 定义 pom 文件3.3 完整代码3.4 总结四 高并发问题4.1 缓存穿透4.2 缓存击穿4…

全方位解读智能中控屏发展趋势!亚马逊Alexa语音+Matter能力成必备

随着智能家居行业逐步从碎片化的智能单品阶段,迈向体验更完整的全屋互联阶段,智能中控屏作为智能家居最佳的入口之一,在年轻人青睐全屋智能装修的风潮下,市场潜力彻底被引爆。 一、为什么是智能中控屏? 在智能音箱增…

诗一样的代码命名规范

有文化:落霞与孤鹜齐飞,秋水共长天一色;没文化:太阳落山的时候,看见一只鸟在水上飞;日常编码中,代码的命名是个大的学问。能快速的看懂开源软件的代码结构和意图,也是一项必备的能力…

Docker入门建议收藏 第二部分

二、Docker 容器技术与虚拟机的区别 Docker 到底是个什么东西呢?我们在理解 Docker 之前,首先得先区分清楚两个概念,容器和虚拟机。 虚拟机 虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在…

单链表的头插,尾插,头删,尾删等操作

前言顺序表要求是具有连续的物理空间,并且数据的话是在这些空间当中是连续的存储。但这样会带来很多问题,比如说在头部或者说中间插入的话,效率不是很高;并且申请空间可能需要扩容,并且越往后一般来说都是异地扩容&…

优思学院|精益生产中的“单件流”真的能够做到吗?

精益生产中提到的“一个流”(One Piece Flow)是一种生产方式,它的核心理念是通过合理配置作业场地、人员和设备,使产品从投入到成品产出的整个制造加工过程中始终处于不停滞、不堆积、不超越,按节拍一个一个地流动。 …

Idea+maven+spring-cloud项目搭建系列--11 整合dubbo

前言: 微服务之间通信框架dubbo,使用netty (NIO 模型)完成RPC 接口调用; 1 dubbo 介绍: Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提…

渲染十万条数据就把你难住了?不存在的!

虚拟列表的使用场景如果我想要在网页中放大量的列表项,纯渲染的话,对于浏览器性能将会是个极大的挑战,会造成滚动卡顿,整体体验非常不好,主要有以下问题:页面等待时间极长,用户体验差CPU计算能力…

pyqt5(二) 标签(QLabel)组件的属性说明及示例

使用语法 widget QLable() widget.function(parameter) widget:实例化QLablefunction:QLable里的函数parameter:函数需要用到的参数 参数说明: 参数说明参数解释 setText() 配置文本内容 setPixmap() 添加图片 setFixedSize(…

蓝桥杯--等差素数列

等差素数列 技巧 这里的等差数列–首项需要枚举列出 公差也需要枚举列出 在公差为1开始&#xff0c;对n-1也进行枚举 //重要代码段 判断一个数是否为素数 int check(int n) { for(int i2;i<n;i){if(n%i0){return 0 } return 1; } }这道题不是很简单 本题为填空题&#xff0…

Webstorm使用、nginx启动、FinalShell使用

文章目录 主题设置FinalShellFinalShell nginx 启动历史命令Nginx页面发布配置Webstorm的一些常用快捷键代码生成字体大小修改Webstorm - gitCode 代码拉取webstorm 汉化webstorm导致CPU占用率高方法一 【忽略node_modules】方法二 【设置 - 代码编辑 - 快速预览文档 - 关闭】主…

Linux 练习七 (IPC 共享内存)

文章目录System V 共享内存机制&#xff1a;shmget shmat shmdt shmctl案例一&#xff1a;有亲缘关系的进程通信案例二&#xff1a;非亲缘关系的进程通信内存写端write1.c内存读端read1.c案例三&#xff1a;不同程序之间的进程通信程序一&#xff0c;写者shmwr.c程序二&#xf…

2022-06-14至2022-08-11 关于复现MKP算法的总结与反思

Prerequisite 自2022年6月14日至2022年8月11日的时间内&#xff0c;我致力于完成A Hybrid Approach for the 0–1 Multidimensional Knapsack problem 论文的复现工作&#xff0c;此次是我第一次进行组合优化方向的学习工作&#xff0c;下面介绍该工作内容发展过程以及该工作结…

JavaScript Array 数组对象实例集合

文章目录JavaScript Array 数组对象实例集合创建数组合并两个数组 - concat()合并三个数组 - concat()用数组的元素组成字符串 - join()删除数组的最后一个元素 - pop()数组的末尾添加新的元素 - push()反转一个数组中的元素的顺序 - reverse()删除数组的第一个元素 - shift()从…