【数据结构】10道经典面试题目带你玩转链表

🦄个人主页:修修修也

🎏所属专栏:数据结构

⚙️操作环境:Visual Studio 2022


目录

一.移除链表元素

二.反转链表

三.链表的中间结点

四.链表中倒数第K个结点

五.合并两个有序链表

六.链表分割

七.链表的回文结构

八.相交链表

九.环形链表


一.移除链表元素

题目链接:

203. 移除链表元素icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/


题目描述:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点


题目详情:


解题思路:

思路一:双指针删除val法

如图,我们创建两个指针,分别是prev和cur,在初始时,它们一个指向空,一个指向链表的第一个结点(即head):

然后让两指针顺着链表向后移动,如果碰到cur不是val的值的结点就继续同时向后移动:

如果在移动的过程中碰到了cur是val的值的结点则将prev链上cur的下一个结点,并将cur结点释放:

删除后prev不动,cur继续向后移动:

如果cur指向的结点不是val的值,则两个指针继续向后移动:

直到cur再次碰到值等于val的结点:

继续将prev链上cur的下一个结点,并将cur结点释放:

删除后prev不动,cur继续向后移动:

这时发现cur已经走到NULL,则链表已经遍历到尾,即已删除完毕,向主函数返回此时链表的头指针head即可.


思路二:双链表遍历尾插法

如图,我们再创建一个单链表newhead,然后创建一个cur指针负责遍历待删链表,再创建一个tail指针负责记录新链表的尾结点:

当cur结点的值不为val时,我们将该结点尾插到新链表的后面:

然后cur指针继续向后移动遍历旧链表:

碰到cur结点的值不为val时,我们继续将该结点尾插到新链表的后面:

然后cur指针向后走,tail指针同样要重新指向新链表的尾结点:

当cur结点的值为val时,我们创建一个新指针next记录下cur的next结点,然后将cur结点free掉,再使cur指针指向刚刚记录的next结点.

然后cur继续向后遍历,遇到值为val的结点就删除,遇到非val的结点就尾插到新结点:

注意,当最后一个结点也是我们要删除的结点时,我们在删除结束后记得要将tail的指针域置为空,否则会导致新链表的尾结点末端连着一个非法空间!

如上,删除结束后返回新链表的头指针newhead即可.


解题代码:

思路一解题代码:

//双指针删val法
struct ListNode* removeElements(struct ListNode* head, int val)
{struct ListNode*cur=head;struct ListNode*prev=NULL;while(cur){if(cur->val != val){prev=cur;cur=cur->next;}else{if(prev == NULL) //如果是头删,则更新头指针,然后删除头结点//否则会造成prev空指针解引用问题{head=cur->next;free(cur);cur=head;}else{prev->next=cur->next;free(cur);cur=prev->next;}}}return head;
}

提交运行:



思路二解题代码:

//双链表遍历尾插法
struct ListNode* removeElements(struct ListNode* head, int val)
{struct ListNode*cur=head;struct ListNode*newhead=NULL;struct ListNode*tail=NULL;while(cur){if(cur->val!=val){//尾插if(newhead==NULL)//头插是赋值{newhead=tail=cur;}else{tail->next=cur;tail=tail->next;}cur=cur->next;}else{struct ListNode*next=cur->next;free(cur);cur=next;}}if(tail!=NULL)//防止最后一个位置是待删元素,free后新链表的尾结点的指针域是野指针问题.{tail->next=NULL;}return newhead;
}

提交运行:


二.反转链表

题目链接:

206. 反转链表icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-linked-list/


题目描述:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。


题目详情:


解题思路:

思路一:三指针逆连链表法

如图,我们创建三个指针分别是p1,p2,p3.使它们分别指向首结点,第二结点,第三结点:

然后将p2指针的next指向p1:

然后使p1,p2,p3向前挪动:

再将p2指针的next指向p1:

然后p1,p2,p3继续向前挪动:

再将p2指针的next指向p1:

再将p1,p2,p3继续向前挪动:

再将p2指针的next指向p1:

再将p1,p2,p3继续向前挪动:

可以看到,当p2指针为空时,链表已经全部逆链完毕,这时返回p1指针即为逆转后的链表头.


思路二:双链表取结点头插法

如图,我们创建一个新链表newhead,以及一个用来遍历待逆置链表的指针cur:

然后使cur向后遍历,每找到一个结点就将其头插到newnode链表中:

继续向后移动然后头插到newnode中:

直到将待逆置链表全部尾插到newhead链表中:

这时链表就逆置完毕了,这时返回newhead,即新链表的头即可.


解题代码:

思路一解题代码:

//三指针逆链表法
struct ListNode* reverseList(struct ListNode* head)
{if(head==NULL){return NULL;}struct ListNode*p1=NULL;struct ListNode*p2=head;struct ListNode*p3=head->next;if(p3==NULL){return head;}else if(p3->next==NULL){p3->next=p2;p2->next=p1;return p3;}else{p1=p2;p2=p2->next;p1->next=NULL;while(p2){p3=p3->next;p2->next=p1;p1=p2;p2=p3;}return p1;}
}

提交运行:


思路二解题代码:

//取结点头插法
struct ListNode* reverseList(struct ListNode* head)
{struct ListNode*newnode=NULL;struct ListNode*cur=head;while(cur){if(newnode==NULL)//当newnode为NULL时单独赋值处理,并把尾结点置空{newnode=cur;cur=cur->next;newnode->next=NULL;}else{struct ListNode*prev=newnode;newnode=cur;cur=cur->next;newnode->next=prev;}}return newnode;
}

提交运行:


三.链表的中间结点

 题目链接:

876. 链表的中间结点icon-default.png?t=N7T8https://leetcode.cn/problems/middle-of-the-linked-list/


题目描述:

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。


题目详情:


解题思路:

快慢指针解题法:

如图,我们分别创建两个指针,一个快指针,一个慢指针,开始时它们都指向链表的头结点:

然后我们让两指针循环向后遍历链表,但我们每次让fast指针向后走两步,而slow指针只向后走一步,如下是两个指针前进一次的样子:

如此循环前进,直到fast走到链表的最后一个结点,即fast->next=NULL时:

可以看到,当fast走到尾时,slow指针恰好指向链表的中间结点.

当然,当链表的结点数为偶数时,则当fast走到NULL时,slow指针恰好指向链表的中间结点,如:


解题代码:

//快慢指针解题法
struct ListNode* middleNode(struct ListNode* head)
{struct ListNode*fast=head;struct ListNode*slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;}return slow;    
}

提交运行:


四.链表中倒数第K个结点

 题目链接:

链表中倒数第K个结点icon-default.png?t=N7T8https://www.nowcoder.com/share/jump/1020746871700211094739


题目描述:

输入一个链表,输出该链表中倒数第k个结点。


题目详情:


解题思路:

快慢指针法:

如图,我们设置两个指针,一个是fast,一个是slow,开始时它们分别指向链表的头结点:

在开始时,我们求链表中的倒数第k个结点,就先让fast向前走k步,如我们要求上图链表中的倒数第2个结点,则我们先让fast指针向前走2步:

当fast走完k步后,fast开始和slow一起向后挪动,直到fast走到NULL为止:

一起向后走一步:

一起向后再走一步:

当fast走到NULL时,slow恰好指向链表的倒数第二个结点:

这时我们在函数中返回slow指针即可.


解题代码:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{//快慢指针法struct ListNode*fast=pListHead;struct ListNode*slow=pListHead;while(k){if(fast==NULL)//防止k的长度比链表的长度长{return NULL;}fast=fast->next;k--;}while(fast){fast=fast->next;slow=slow->next;}return slow;
}

提交运行:


五.合并两个有序链表

 题目链接:

21. 合并两个有序链表icon-default.png?t=N7T8https://leetcode.cn/problems/merge-two-sorted-lists/


题目描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 


题目详情:


解题思路:

双指针尾插新链表法:

如图,我们创建一个新链表,以及三个指针,分别是cur1,cur2,tail,分别用来遍历链表1,遍历链表2,记录新链表的尾结点:

然后我们比较cur1和cur2的值,将其中的较小者尾插到newhead链表中(假设两个值相同时我们默认将cur1插入到链表中):

然后分别更新cur1指针和tail指针,使它们分别指向下一个结点和新链表的尾结点:

然后再比较cur1和cur2的值,将它们的较小者插入到新链表中:

再更新cur2指针和tail指针:

直到某一链表的结点全部插入到newhead中,如:

这时将不为空的链表剩下的全部结点直接链接在tail指针后面即可:

这时得到的新链表就是两个链表合并为升序链表的结果,我们返回newhead即可.


解题代码:

//双指针比较尾插新链表法
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{struct ListNode*cur1=list1;struct ListNode*cur2=list2;struct ListNode*newhead=NULL;struct ListNode*tail=newhead;while(cur1&&cur2){if(cur1->val<=cur2->val){if(newhead==NULL)//首结点是赋值{newhead=cur1;tail=newhead;}else{tail->next=cur1;tail=tail->next;}cur1=cur1->next;}else{if(newhead==NULL)//首结点是赋值{newhead=cur2;tail=newhead;}else{tail->next=cur2;tail=tail->next;}cur2=cur2->next;}}if(cur1==NULL){if(tail)//防止tail为NULL时的空指针解引用问题{tail->next=cur2;}else{return cur2;}}else{if(tail){tail->next=cur1;}else{return cur1;}}return newhead;
}

提交运行:


六.链表分割

 题目链接:

链表分割icon-default.png?t=N7T8https://www.nowcoder.com/share/jump/1020746871700135186269


题目描述:

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。


题目详情:


解题思路:

双链表分类再合并法:

如图,我们创建一个cur指针用来遍历链表,然后创建一个新链表newhead1来存放比x小的结点,同时创建一个tail1指针记录这个链表的尾结点.

再创建一个新链表newhead2来存放比x大的结点,同时创建一个tail2指针记录这个链表的尾结点:

我们以x等于3为例,如果cur指向的结点的val值小于3,就将该结点尾插到newhead1链表中,反之,如果cur指向的结点的val值大于等于3,就将该结点尾插到newhead2链表中:

我们将cur指向的'1'结点尾插到newhead1中,并用tail1记录下newhead1的尾结点,然后cur向后遍历:

再将'2'结点尾插到newhead1链表中,更新tail1和cur的指向:

这时cur指向的结点的val值为6,大于x,我们把它尾插到newhead2中,更新cur和tail2的值:

将后续结点依次判断后尾插到newhead1或newhead2中去,直到cur为NULL:

当将原链表中的结点全部分到两个新链表中后,将newhead1和newhead2链表重新链接在一起,即tail1的指针域连接上newhead2,就可以得到一个新的按题目要求排序好的新链表了:

这时返回新链表的头,即newhead1给函数即可.


解题代码:

//双链表分类再合并法
class Partition {
public:ListNode* partition(ListNode* pHead, int x){struct ListNode*cur=pHead;struct ListNode*newhead1=NULL;struct ListNode*tail1=NULL;struct ListNode*newhead2=NULL;struct ListNode*tail2=NULL;while(cur){if(cur->val<x){if(newhead1==NULL){newhead1=cur;tail1=newhead1;}else {tail1->next=cur;tail1=tail1->next;}}else{if(newhead2==NULL){newhead2=cur;tail2=newhead2;}else {tail2->next=cur;tail2=tail2->next;}}cur=cur->next;}if(tail2!=NULL)//防止tail2尾结点的指针域不为空造成新链表带环{tail2->next=NULL;//所以要将tail2的指针域置为空if(tail1!=NULL){tail1->next=newhead2;//置空之后链接两个链表return newhead1;    //链接成功返回newhead1}else{return newhead2;   //tail1==NULL意味newhead1没有元素,则可以直接返回newhead2}}else     //tail2==NULL意味newhead2没有元素,则可以直接返回newhead1{return newhead1;}return newhead1;}
};

提交运行:


七.链表的回文结构

 题目链接:

链表的回文结构icon-default.png?t=N7T8https://www.nowcoder.com/share/jump/1020746871700305446248


题目描述:

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

如:

1->2->2->1

返回:

true

题目详情:


解题思路:

先逆置后对比对法:

思路为:我们先将原链表的后半段逆置,然后再将其和原链表的前半段作对比,如果相同,则是回文结构.

实现该思路我们需要先找中间结点,再逆置中间结点后的链表部分,再将逆置后的链表和原链表的前半部分做对比:

图示如下,找中间结点:

逆置中间结点后的链表得到newhead:

逐一对比head链表和newhead链表的结点,如果有不一样的,则不是回文结构,如果都一样,则是回文结构:

注:查找中间结点以及逆转链表的代码思路我们在前面几道题中已经实现过了,在这里直接套用即可.


解题代码:

//先逆转再对比法
ListNode*midnode(ListNode*head)//找中间结点函数
{struct ListNode*fast=head;struct ListNode*slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;}return slow;
}ListNode* reverseList(struct ListNode* head)//逆转函数
{struct ListNode*newnode=NULL;struct ListNode*cur=head;while(cur){if(newnode==NULL)//当newnode为NULL时单独赋值处理,并把尾结点置空{newnode=cur;cur=cur->next;newnode->next=NULL;}else{struct ListNode*prev=newnode;newnode=cur;cur=cur->next;newnode->next=prev;}}return newnode;
}class PalindromeList {
public:bool chkPalindrome(ListNode* A) {//找中间结点   slowstruct ListNode*mid=midnode(A);//逆置尾链表struct ListNode*newhead=reverseList(mid);//对比新链表struct ListNode*cur1=A;struct ListNode*cur2=newhead;while(cur1&&cur2){if(cur1->val!=cur2->val){return false;}cur1=cur1->next;cur2=cur2->next;}return true;}
};

提交运行:


八.相交链表

 题目链接:

160. 相交链表icon-default.png?t=N7T8https://leetcode.cn/problems/intersection-of-two-linked-lists/


题目描述:

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构


题目详情:


解题思路:

对于这道题,我们首先要理解:假设两个链表短的长度为x,长的长度为y,则他们的交点一定位于长链表的后x结点中,而不会位于后x之外的结点:

因为交点之后的结点就是两个链表共用的了,即相交后的链表元素如果A有那么B也一定有,因此不可能出现两链表的交集比某一链表的长度还长的情况.

所以我们在找交点时应该从长链表的第y-x个结点和短链表的第一个结点开始一起向后找有没有相同的结点:

如果有,则为两链表的交点,如果没有,则两链表没有交点:

因此我们本题的思路是,先分别求出A,B链表的长度,然后让长链表向后走到后面只有x个结点的地方,开始和短链表一起向后移动,在移动的过程中找有没有交点,如果找到了,返回交点,如果走到尾还没有找到,则返回0.


解题代码:

//同长后移找交点法
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{//先遍历A,B链表求出长度struct ListNode*cur1=headA;struct ListNode*cur2=headB;int len1=0;int len2=0;while(cur1){cur1=cur1->next;len1++;}while(cur2){cur2=cur2->next;len2++;}int k=abs(len1-len2);//让长的先走到和短的长度一样的地方,然后开始同时向后走cur1=headA;cur2=headB;if(len1>len2){while(k--){cur1=cur1->next;}while(cur1&&cur2){if(cur1==cur2){return cur1;}cur1=cur1->next;cur2=cur2->next;}return 0;}else{while(k--){cur2=cur2->next;}while(cur1&&cur2){if(cur1==cur2){return cur2;}cur1=cur1->next;cur2=cur2->next;}return 0;}
}

提交运行:


九.环形链表

 题目链接:

141. 环形链表icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle/


题目描述:

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false


题目详情:


解题思路:

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾.

现实中参考陪女朋友在操场跑步减肥时套圈的情景.


解题代码:

//双指针循环追逐法
bool hasCycle(struct ListNode *head)
{if(head==NULL||head->next==NULL){return false;}struct ListNode*fast=head;struct ListNode*slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;if(fast==slow){return true;}}return false;
}

提交运行:


结语

希望通过上面的题目能使大家对链表的理解更上一层楼,欢迎大佬们留言或私信与我交流.学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

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

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

相关文章

【心得】PHP文件包含基本利用个人笔记

本文可能比较凌乱&#xff0c;快速总结保证自己看得懂&#xff08;真.个人笔记&#xff09; 文件包含的本质&#xff1a;代码复用、并行开发、模块化、增加移植性 include和eval的区别&#xff1a; include和eval一样&#xff0c;都不是函数&#xff0c;都是语言结构&#xf…

聊聊近些年 CPU 在微架构、IO 速率上的演进过程

大家好&#xff0c;我是飞哥&#xff01; 在上一篇《深入了解 CPU 的型号、代际架构与微架构》 中我们介绍了我手头的一颗 Intel(R) Core(TM) i5 的型号规则&#xff0c;以及它的物理硬件的 Die 图结构。以及它对应的 Skylake 核的微架构实现。 不少同学开始问我其它型号的 CPU…

遗传算法GA-算法原理与算法流程图

本站原创文章&#xff0c;转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一、遗传算法流程图 1.1. 遗传算法流程图 二、遗传算法的思想与机制 2.1 遗传算法的思想 2.2 遗传算法的机制介绍 三、 遗传算法的算法流程 3.1 遗传算法的算法…

一文总结MySQL的指令是如何工作的

当你输入一条MySQL指令时候有没有想过会发生什么&#xff1f; 建立连接 首先你得先连到数据库上才行&#xff0c;这又分为长连接和短链接&#xff0c;短链接就是你查询一次就断开连接&#xff0c;长连接是你可以多次查询直到主动断开连接&#xff08;也可能被杀死进程&#x…

4.3每日一题(知全微分求函数本身)

公式 1、先通过公式&#xff1a;dx前系数对y求偏导、dy前面的系数对x求偏导&#xff0c;求出 f(x)的表达式&#xff1b;对x求不定积分&#xff0c;再通过 f(0)0求出常数C&#xff0c;即可求出 f(x) 2、把F(x)的全微分列出来&#xff0c;dx、dy前面的表达式分别为F(x)偏x、y的…

移动端路径传参以数字的形式,写死的情况

页面1 async getListTransferAndApprova() { //把mark值拼接到路径的后面&#xff0c;定义一个变量&#xff0c;使得切换穿的mark都不一样let mark ;if (this.tabsCurrent 0) {mark 2;} else if (this.tabsCurrent 1) {mark 3;}else if (this.tabsCurrent 2) {mark 4;}…

IDEO也不行了吗?设计正在变革#实时设计

2023 年 8 月&#xff0c;在与宜家品牌合作近 10 年之后&#xff0c;SPACE10 关门了。 最近&#xff0c;IDEO&#xff0c;设计思维的早期倡导者和践行者&#xff0c;宣布裁员1/3。 介绍下这两家设计公司&#xff1a; SPACE10 由宜家全额资助&#xff0c;于 2015 年落户哥本哈根…

微信小程序开发学习——页面布局、初始导航栏与跳转

1.盒模型 要求实现效果如图所示&#xff1a; 所有WXML元素都可以看作盒子&#xff0c;在WXSS中"box model”这一术语是用来设计和布局时使用盒模型本质上是一个盒子&#xff0c;封装周围的WXML元素它包括: 边距&#xff0c;边框&#xff0c;填充和实际内容&#xff0c;模…

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(八)

FULL OUTER JOIN 除了前面讲到的 INNER JOIN&#xff08;内连接&#xff09;、LEFT JOIN&#xff08;左连接&#xff09;、RIGHT JOIN&#xff08;右连接&#xff09;&#xff0c;还有另外一种关联方式&#xff0c;即 FULL OUTER JOIN&#xff08;全外连接&#xff09; FULL O…

C/C++数据结构之堆栈(Stack):理解、实现与运用

当我们讨论堆栈时&#xff0c;我们首先需要了解它的概念和基本原理。堆栈是一种后进先出&#xff08;Last In, First Out&#xff0c;LIFO&#xff09;的数据结构&#xff0c;它的操作主要包括压栈&#xff08;Push&#xff09;和弹栈&#xff08;Pop&#xff09;&#xff0c;以…

webpack 中,filename 和 chunkFilename 的区别

filename filename 是一个很常见的配置&#xff0c;就是对应于 entry 里面的输入文件&#xff0c;经过webpack打包后输出文件的文件名。比如说经过下面的配置&#xff0c;生成出来的文件名为 index.min.js。 chunkFilename chunkFilename 指未被列在 entry 中&#xff0c;却…

Java 教育局民办教育信息服务与监管平台

1) 项目背景 按照《中华人民共和国民办教育促进法》和《中华人民共和国政府信息公开条例》的相关规定&#xff0c;为满足学生和家长、社会各界获取权威信息的需求&#xff0c;着力解决服务老百姓最后一公里问题&#xff0c;达到宣传民办教育和引导家长择校的效果&#xff0…