【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构

文章目录
- 一、移除链表元素
- 思路一
- 思路二
- 二、合并两个有序链表
- 思路:
- 优化:
- 三、反转链表
- 思路一
- 思路二
- 四、链表的中间节点
- 思路一
- 思路二
- 五、综合应用之链表的回文结构
- 思路一:
- 思路二:
一、移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
我们先来看看题目描述和第一个示例:

根据题目描述我们就可以大致明白题意,就是将一个链表中的某个值的节点删除,然后返回新链表的头结点,然后题目要我们实现的函数给了我们头结点,以及要删除的数据,我们要把相应的节点删除
思路一
首先最简单的思路就是,我们可以通过之前实现的链表的方法用上,首先使用Find方法找到对应的值,然后使用Erase方法删除,直到Find方法返回空指针结束
由于这个方法思路比较好实现,这里就不再赘述了,可以自己尝试一下,我们的关键是更优方法的思路二
思路二
这个题其实跟我们在刷顺序表题的时候遇见类似的,只不过之前要删除的是数组中的元素,这道题是删除链表节点,不过本质上是相同的,上次我们使用了双指针,这次我们还是可以使用双指针,顺序表刷题参考:【初阶数据结构与算法】沉浸式刷题之顺序表练习(顺序表以及双指针两种方法)
具体思路也很像之前的那个题,题目让返回新链表的头结点,没有说必须是原链表的头结点,所以我们可以新建一个链表,如果遍历到原链表中节点的值不是题目给定的值,也就是不是我们要删除的节点,那么我们就把它尾插到新链表
我们要注意的是,如果遇到了要插入的节点,但是新链表的头为空,我们就要让新链表的头和尾都指向这个节点,其它情况就正常尾插
还有一个重要的地方就是,当我们把链表移动完毕之后,新链表的尾结点可能还指向原链表的节点,我们要把它置为空,题解如下:
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val)
{ListNode* newhead, *newtail;newhead = newtail = NULL;ListNode* pcur = head;while(pcur){if(pcur->val != val){if(newhead == NULL){newhead = newtail = pcur;}else{newtail->next = pcur;newtail = pcur;}}pcur = pcur->next;}if(newtail)newtail->next = NULL;return newhead;
}

二、合并两个有序链表
题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/
我们先来看看题目的描述和第一个示例:

题目给我们两个有序链表,要求我们把这两个链表合并成一个新的有序链表,然后返回它的头结点
思路:
这个问题是不是有点似曾相识,跟我们之前的合并有序数组是一样的,我们当时的方法就是使用双指针,只是合并有序数组时是要求我们在第一个数组中进行修改,不能新建一个数组返回
但是这道题还要简单一些,它可以新建一个链表,我们可以通过双指针遍历要合并的链表,比较两个链表中节点的大小,谁小谁就尾插到新链表,直到有一个链表走到空就停止循环
但是我们要注意的一点是,虽然有一个链表走到空了,也就是一个链表中的节点都插入到新链表了,但是另一个链表可能还有节点,所以我们要判断一下,如果两个链表中还有一个链表不为空,那么直接将它的所有节点尾插到新链表
还有就是做一个特殊处理,因为两个链表中可能有空链表,上面的方法就跑不通,所以我们判断一下,如果有一个链表为空,那么直接返回另一个链表,题解如下:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{if(list1 == NULL){return list2;}if(list2 == NULL){return list1;}ListNode* pcur1, *pcur2;ListNode* newhead, *newtail;pcur1 = list1;pcur2 = list2;newhead = newtail = NULL;while(pcur1 && pcur2){if(pcur1->val < pcur2->val){if(newhead == NULL){newhead = newtail = pcur1;}else{newtail->next = pcur1;newtail = pcur1;}pcur1 = pcur1->next;}else{if(newhead == NULL){newhead = newtail = pcur2;}else{newtail->next = pcur2;newtail = pcur2;}pcur2 = pcur2->next;}}if(pcur1){newtail->next = pcur1;}if(pcur2){newtail->next = pcur2;}return newhead;
}

优化:
上面的代码是一种题解,但是我们可以发现,这个代码写起来有点麻烦,有一些重复的动作,就是在每次插入之前,我们要判断链表是否为空,如果为空要让新链表的头和尾都指向要插入的节点
那我们能不能让代码更加简洁一点呢?就是不必每次插入节点前都判断链表是否为空,这里就可以用上我们之前学过的带头的概念,我们申请一个不保存数据的哨兵位当作链表默认的头
这样我们的新链表默认就有了一个节点,不为空了,可以直接在哨兵位后面尾插节点,不用判断链表是否为空,最后返回时就返回哨兵位的下一个节点就可以了,它就是我们新链表中保存数据的头节点
不过由于我们的哨兵位是通过malloc来的,所以最后代码结束时不要记得把它释放掉,以免造成内存泄漏,如下:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{if(list1 == NULL){return list2;}if(list2 == NULL){return list1;}ListNode* pcur1, *pcur2;pcur1 = list1, pcur2 = list2;ListNode* newhead, *newtail;newhead = newtail = (ListNode*)malloc(sizeof(ListNode));while(pcur1 && pcur2){if(pcur1-> val < pcur2->val){newtail->next = pcur1;newtail = pcur1;pcur1 = pcur1->next;}else{newtail->next = pcur2;newtail = pcur2;pcur2 = pcur2->next;}}if(pcur1){newtail->next = pcur1;}if(pcur2){newtail->next = pcur2;}ListNode* ret = newhead->next;free(newhead);newhead = NULL;return ret;
}

三、反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
我们来看看题目描述和第一个示例:

题目要求我们将给出的链表反转,就是改变指针的指向,让原本的尾节点变成头,让原本的头结点变成尾
思路一
思路一还是很简单,就是我们创建一个新链表,遍历原链表,拿原链表中的节点头插到新链表就可以了,如图:


有了上图的分析,实现就很简单了,只需要一个头插方法,我们之前讲过,这里就不再赘述了,可以自己试试,我们重点介绍思路二
思路二
思路二比较难想出来,但是确实非常快,因为它是对原链表的节点的指针指向进行修改,所以很快,并且不会消耗什么空间,思路如图:


有了上面的思路我们就可以来写代码了,但是我们要注意一个点,就是如果题目直接给出一个空链表让我们反转,那么我们对它解引用就会出错,所以我们特殊处理一下,如果链表为空就直接返回,空链表反转还是空链表,题解如下:
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{if(head == NULL){return head;}ListNode* n1, *n2, *n3;n1 = NULL;n2 = head;n3 = head->next;while(n2){n2->next = n1;n1 = n2;n2 = n3;if(n3)n3 = n3->next;}return n1;
}

四、链表的中间节点
题目链接:https://leetcode.cn/problems/middle-of-the-linked-list/
我们来看看题目描述和两个示例,如下:

它的要求就是让我们返回链表的中间节点,如果是偶数个节点,那么就有两个中间节点,则返回后一个节点
思路一
我们首先能想到的思路就是,先遍历整个链表,看看链表一共有多少个节点,然后让它除以2,最后再次循环遍历链表就可以找到中间节点,这个题很简单,我们直接给出题解,如下:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{int count = 0;ListNode* pcur = head;while(pcur){count++;pcur = pcur->next;}count /= 2;pcur = head;while(count--){pcur = pcur->next;}return pcur;
}

虽然这个方法看起来已经很简单了,但是始终都是执行了两次循环,有没有更简单的方法呢?接下来我们来看看思路二
思路二
思路二的方法很绝妙,简单又快捷,就是使用快慢指针的算法,快慢指针默认都指向头结点,慢指针一次走一步,快指针一次走两步,那么等快指针走到空的时候,慢指针指向的节点就是中间节点
为什么呢?因为快指针每次走的距离都是慢指针的2倍,最后统计一共走的距离时,快指针走的总距离也是慢指针的2倍,而快指针走到了空,也就说明走到了链表尾,那么此时慢指针就是它的一半,刚好指向中间节点,题解如下:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{ListNode* slow, *fast;slow = fast = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;}return slow;
}

五、综合应用之链表的回文结构
题目链接:https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa
我们先来看看题目描述和它的第一个示例:

题目要求我们判断给出的链表是否是一个回文结构,也就是是否前后对称,这道题就可以用上我们之前做题写出的代码,具体后面再说,我们先解决一个事情
就是这个题目没有提供C语言的选项,那我们就选择C++来做,C++是兼容C语言的,主要是我们要知道在哪里写代码,如图:

这是C++中的类,但是不影响我们做题,我们只需要知道我们把代码写在哪里,在题目中也有提示,把代码写在紫色大括号内即可,其它的可以不管,还有一个就是,C++对结构体做了优化,可以在使用结构体时不必加上struct
思路一:
虽然判断链表是否是回文结构很难,但是我们可以把链表中的数据存放到数组中,判断数组是否是回文结构,这个就比较简单了
由于链表两边的数据是对称的,所以我们定义一个left和right分别指向数组的头和尾,然后对比它们的值,如果不同直接返回假,否则的话就一直让它们往中间走,直到left不再小于right
在循环过程中,一旦left所在位置的值和right所在位置的值不相同,就说明并不对称,也就不是回文结构,返回假,一旦循环结束,说明左右对称,就是回文结构,直接返回真
并且我们注意到,虽然题目要求空间复杂度为O(1),但是同时又给出了链表的最大节点个数不超过900,那定义一个901个元素大小的数组时间复杂度还是O(1),因为它始终还是常数个空间,如下:
class PalindromeList {
public:bool chkPalindrome(ListNode* A) {int arr[901] = { 0 };ListNode* pcur = A;int i = 0;while(pcur){arr[i++] = pcur->val;pcur = pcur->next;}int left = 0, right = i-1;while(left < right){if(arr[left] != arr[right]){return false;}left++, right--;}return true;}
};

最后就是,这个方法虽然很简单,但是只适合给出了链表节点大小的题目,如果遇到没有给出链表节点大小的题目就会导致时间复杂度变成O(N),导致不符合要求,所以我们再介绍一个方法
思路二:
这个思路可以帮我们复习上面做过的题,让我们能够灵活运用知识,具体思路就是,我们首先通过找中间节点的函数找到链表中间节点,然后从中间节点开始,将后面的节点反转,形成一个新链表,然后再和原链表进行比较即可,如图:

找中间节点的函数和反转链表的函数可以从我们之前做过的题里面拿过来用,当然也可以自己根据这个逻辑把中间的代码实现,这里我就直接把之前写过的函数直接拿过来用,如下:
class PalindromeList {public:struct ListNode* reverseList(struct ListNode* head) {if (head == NULL) {return head;}ListNode* n1, *n2, *n3;n1 = NULL;n2 = head;n3 = head->next;while (n2) {n2->next = n1;n1 = n2;n2 = n3;if (n3)n3 = n3->next;}return n1;}struct ListNode* middleNode(struct ListNode* head) {ListNode* slow, *fast;slow = fast = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;}return slow;}bool chkPalindrome(ListNode* A) {if(A == NULL){return true;}ListNode* mid = middleNode(A);ListNode* newlist = reverseList(mid);ListNode* pcur1, *pcur2;pcur1 = A, pcur2 = newlist;while(pcur2){if(pcur1->val != pcur2->val){return false;}pcur1 = pcur1->next;pcur2 = pcur2->next;}return true;}
};

那么今天的链表刷题训练就到这里结束啦,有什么不懂欢迎提出,我们下一篇文章还是刷题,之后我们会讲栈和队列的实现,敬请期待
bye~
相关文章:
【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
文章目录 一、移除链表元素思路一思路二 二、合并两个有序链表思路:优化: 三、反转链表思路一思路二 四、链表的中间节点思路一思路二 五、综合应用之链表的回文结构思路一:思路二: 一、移除链表元素 题目链接:https:…...
【PGCCC】Postgresql Toast 原理
前言 上篇博客讲述了 postgresql 如何存储变长数据,它的应用主要是在 toast 。Toast 在存储大型数据时,会将它存储在单独的表中(称为 toast 表)。因为 postgresql 的 tuple(行数据)是存在在 Page 中的&…...
vue3使用element-plus,树组件el-tree增加引导线
vue3使用element-plus,树组件el-tree增加引导线 vue3项目element-plus,树组件el-tree增加引导线 element-plus组件库的el-tree样式 因为element的样式不满足当前的的需求,UI图,所以对el-tree进行增加了引导线 修改样式如下&am…...
AlphaFold3中文使用说明
目录 1. 在线网站用例1. 使用json输入预测蛋白结构 2. 本地命令行2.1 运行示例2.2 AF3输入输入格式JSON兼容性JSON最外层(Top-level)结构序列多序列比对MSA结构模板键 用户提供CCDs 2.3 AF3输出 AlphaFold3(AF3)可以通过在线网站或…...
使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件
前言 假设您正在现有项目中集成这些包,而该项目的构建工具为 Webpack 或 Vite。同时,您对 Three.js 和 React 有一定的了解。如果您发现有任何错误或有更好的方法,请随时留言。 安装 npm install three types/three react-three/fiber rea…...
Koa进阶:掌握中间件和参数校验的艺术
目录 一、首先下载依赖 二、在index.js中引入koa-parameter,一般挂载这个中间件时会放在注册请求体的后面 三、使用实例 四、如果跟我们所需求的参数不同,返回结果直接会返回422 koa-parameter一般是用来校验请求传过来的参数是否是自己所需要的的 G…...
开源共建 | 长安链开发常见问题及规避
长安链开源社区鼓励社区成员参与社区共建,参与形式包括不限于代码贡献、文章撰写、社区答疑等。腾讯云区块链王燕飞在参与长安链测试工作过程中,深入细致地总结了长安链实际开发应用中的常见问题及其有效的规避方法,相关内容多次解答社区成员…...
【网络】深入理解 HTTPS:确保数据传输安全的核心协议
目录 引言一、HTTPS的基本概念1.1 什么是 HTTPS?1.2 HTTPS 的工作原理1.3 图解:HTTPS 通信过程1.4 HTTPS 与 HTTP 的区别1.5 为什么 HTTPS 更加重要? 二、SSL/TLS协议的核心2.1 SSL/TLS 协议的作用2.2 SSL/TLS 的工作流程2.2.1 握手阶段2.2.2…...
C/C++中使用MYSQL
首先要保证下载好mysql的库和头文件,头文件在/usr/include/mysql/目录下,库在/usr/lib64/mysql/目录下: 一般情况下,在我们安装mysql的时候,这些都提前配置好了,如果没有就重装一下mysql。如果重装mysql还是…...
【GD32】(一) 开发方式简介及标准库开发入门
文章目录 0 前言1 开发方式选择2 标准库模板的创建3 遇到的问题和解决方法 0 前言 因为项目关系,需要使用GD32。之前对此早有耳闻,知道这个是一个STM32的替代品,据说甚至可以直接烧录STM32的程序(一般是同型号)&#x…...
轻松上手:使用Docker部署Java服务
文章目录 1. 什么是Docker?2. 为什么使用Docker部署Java服务?3. 如何使用Docker部署Java服务?步骤1:创建Dockerfile步骤2:构建Docker镜像步骤3:运行Docker容器 4. 注意事项5. 结语推荐阅读文章 在当今的云计…...
wormml_vgg19
创建环境 mamba install libopencv hdf5 -c conda-forge conda create -n st python3.6.2手动导入包 mamba install blas1.0mkl -c conda-forge mamba install hdf51.8.20hac2f561_1 -c conda-forge mamba install libopencv3.4.2h20b85fd_0 -c conda-forge mamba install l…...
Rust学习(二):rust基础语法Ⅰ
Rust学习(二)——rust基础语法Ⅰ: 1、关键字: 了解编程语言的同学都清楚,关键字在一门编程语言中的意义,所谓关键字就是语言的创造者及后续开发者们,以及定义好的具有特殊含义和作用的单词&am…...
【WebRTC】视频发送链路中类的简单分析(下)
目录 1.任务队列节流发送器(TaskQueuePacedSender)1.1 节流控制器添加RTP数据包(PacingController::EnqueuePacket())1.2 监测是否要处理Packet(PacingController::MaybeProcessPackets()) 2.数据包路由&am…...
HTML(超文本标记语言)
HTML(超文本标记语言 - HyperText Markup Language)是一种用于创建网页的标准标记语言。 HTML 最初是由蒂姆・伯纳斯 - 李(Tim Berners - Lee)在 1990 年左右开发的。当时的目的是为了让世界各地的科学家能够方便地共享和交流信息…...
CatBoost中目标变量统计
CatBoost中的目标变量统计(Target Statistics)是其处理分类特征(Categorical Features)的核心技术之一。目标变量统计是一种特殊的编码方法,通过利用目标值信息生成数值特征,从而替代传统的独热编码或其他处…...
WSL与Ubuntu系统--使用Linux
WSL与Ubuntu系统--使用Linux 前言基础教学视频卸载链接网络配置方法1方法2 正式安装步骤步骤1 基本命令修改网络配置Ubuntu系统的导出与导入文件操作给Ubuntu创造界面--也就是在装一个有界面的UbuntuHyper-v与windows主机文件共享 前言 需要链接梯子,并且梯子十分稳…...
操作系统离散存储练习题
1. (简答题)分页存储管理系统具有快表,内存访问时间为2ns,检索快表时间为0.5ns,快表命中率为80%,求有效访问时间 -分析:首先访问缓存(快表),如果没有找到访问内存(页表&…...
性能高于Transformer模型1.7-2倍,彩云科技发布基于DCFormer架构通用大模型云锦天章
2017年,谷歌发布《Attention Is All You Need》论文,首次提出Transformer架构,掀开了人工智能自然语言处理(NLP)领域发展的全新篇章。Transformer架构作为神经网络学习中最重要的架构,成为后来席卷全球的一…...
PHP反序列化_3-漏洞利用
1. 信息收集与分析 确定目标应用程序:首先需要找到存在反序列化漏洞的 PHP 应用程序。这可能是一个网站、Web 服务、内部系统等。可以通过网络扫描、漏洞报告、安全评估等方式来发现潜在的目标。分析应用程序逻辑:了解目标应用程序的功能和业务逻辑&…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...
