【数据结构】单链表经典算法题的巧妙解题思路

目录

题目

1.移除链表元素

2.反转链表

3.链表的中间节点

4.合并两个有序链表

5.环形链表的约瑟夫问题

解析

题目1:创建新链表

题目2:巧用三个指针

题目3:快慢指针

题目4:哨兵位节点

题目5:环形链表


介绍完了单链表,我们这次来说几个经典的题目,本篇的题目衔接下一章双向链表哦~ 

题目

1.移除链表元素

2.反转链表

3.链表的中间节点

4.合并两个有序链表

5.环形链表的约瑟夫问题

我们来看看本篇要介绍的解法吧 

解析

这里介绍的解析仅供参考,算法题的实现思路是很多的,小编就不一一详细介绍了,我们主要介绍小编认为比较好的方法~

题目1:创建新链表

思路:找值不为val的节点,尾插到新链表中

 pcur不等于val时,就把节点放在新链表中,新链表初始为空链表,所以新链表的第一个节点既是newhead也是newtail,放好后pcur往后走,继续判断节点是否等于val

 pcur所指向的节点不等于val,把这个节点尾插到新链表里面,newhead不变,newtail往后走,pcur也往后走,继续判断

 此时pcur指向的节点值为val,pcur再往后走,其余不变

一直到pcur遍历完原链表,此时新链表就是去掉所有等于val值的节点的链表

 我们代码实现一下

首先把结构体类型 struct ListNode 换个名字,方便定义,就换成ListNode

typedef struct ListNode ListNode;

 然后在题目给的函数中进行实现

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{ListNode* newhead, * newtail;//定义新头节点、新尾节点newhead = newtail = NULL;//新链表初始时为空ListNode* pcur = head;//pcur初始时指向原链表首节点while ()//开始遍历链表{}
}

循环结束的条件应该是pcur不为NULL,循环体里面就是刚刚分析的过程

struct ListNode* removeElements(struct ListNode* head, int val) 
{ListNode* newhead, * newtail;//定义新头节点、新尾节点newhead = newtail = NULL;//新链表初始时为空ListNode* pcur = head;//pcur初始时指向原链表首节点while (pcur)//开始遍历链表{if (pcur->val != val)//找pcur不为val的节点{if (newhead == NULL)//新链表为空{newhead = newtail = pcur;}else//新链表不为空{newtail->next = pcur;newtail = newtail->next;}}pcur = pcur->next;//继续往后遍历}if(newtail)newtail->next = NULL;return newhead;
}

题目2:巧用三个指针

参考思路1:依旧是新创建一个链表,遍历原链表,将原链表中的节点采用头插的形式放在新链表中,就可以实现链表的翻转

参考思路2:创建3个指针,完成原链表的翻转

我们来实现思路2,这个思路一般不太好想

创建3个变量,n1、n2、n3初始时分别指向NULL、节点1、节点2

然后让n2指向n1,n1走到n2的位置,n2走到n3的位置,n3走向下一个节点

然后再重复上面的步骤,n2指向n1,n1走到n2的位置,n2走到n3的位置,n3走向下一个节点,

 走到这里的时候n3就不能继续走了,n1和n2继续走

此时n1就是链表的新头节点

我们来代码实现,依旧是重命名一下,把结构体类型 struct ListNode 换个名字,方便定义,换成ListNode

typedef struct ListNode ListNode;

然后在题目给的函数中实现

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{ListNode* n1, * n2, * n3;//创建3个变量n1 = NULL;//给三个变量赋值n2 = head;n3 = n2->next;while (n2){n2->next = n1;n1 = n2;n2 = n3;if(n3) //n3为不空时才继续往后走n3 = n3->next;;}return n1;
}

这是链表不为空的情况,那链表为空时呢?直接返回 head 就可以了

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{if (head == NULL) //空链表return head;//非空链表ListNode* n1, * n2, * n3;//创建3个变量n1 = NULL;//给三个变量赋值n2 = head;n3 = n2->next;while (n2){n2->next = n1;n1 = n2;n2 = n3;if(n3) //n3为不空时才继续往后走n3 = n3->next;;}return n1;
}

题目3:快慢指针

这道题的思路也很多,本篇想通过这题介绍一种很妙的方法,快慢指针,快慢指针在之后的很多题中也有很多应用

先来介绍一下快慢指针,slow表示慢指针,fast表示快指针

慢指针slow一次往后走一步,快指针fast一次往后走两步

fast没有走到空,继续走,slow一次往后走一步,fast一次往后走两步

fast不能再继续往后走,此时slow所指向的节点就是链表的中间节点,这是节点个数为奇数的情况如果节点个数为偶数个呢?我们来看看

依旧是慢指针slow一次往后走一步,快指针fast一次往后走两步

此时fast走到空了,不再继续往后走,slow所指向的节点正是第二个中间节点

这就很简单了,我们将快慢指针运用到题目中,代码实现一下

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;
}

这里需要注意while循环里面的判断条件 (fast && fast->next) 中 fast 不能和 fast->next 交换位置,因为如果链表结点个数是偶数个时,fast会走到NULL,当fast走到NULL时,while再对fast->next进行判断,此时可以对空指针解引用吗?不可以。所以fast->next不可以放在前面。当fast放在前面,此时fast为NULL,直接退出循环,根本就不会对后面的表达式进行判断,也就不会出现对空指针解引用的情况。

题目4:哨兵位节点

我们先说没有哨兵位的代码,哨兵位后面再分析

这题我们先选择创建新链表,遍历原链表,我们定义两个指针遍历两个链表

值小的节点放在新链表中,相等的话随便放哪个都行,然后让相应的指针往后走一个

此时L1和L2比较,L1比L2小,把L1的节点放到新节点,然后L1往后移

然后再依次向后比较,一直到L1或L2走到NULL为止,遍历结果只有两种情况,要么L1为空,要么L2为空

 我们代码实现一下,先写L1的值小于L2的值的时候

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{ListNode* L1 = list1; //遍历原链表ListNode* L2 = list2;ListNode* newhead, * newtail; //创建新链表newhead = newtail = NULL;while (L1 && L2)//循环遍历,有一个走到NULL就退出{if (L1->val < L2->val)//L1的节点值小{if (newhead == NULL) //新链表为空链表{newhead = newtail = L1;}else //新链表不为空链表{newtail->next = L1;//把L1放在新链表newtail = newtail->next;}L1 = L1->next;//L1往后走}else //L2的节点值小{}}
}

L2小于L1的时候也是类似的,此时while循环内代码为

	while (L1 && L2)//循环遍历,有一个走到NULL就退出{if (L1->val < L2->val)//L1的节点值小{if (newhead == NULL) //新链表为空链表{newhead = newtail = L1;}else //新链表不为空链表{newtail->next = L1; //把L1放在新链表newtail = newtail->next;}L1 = L1->next;//L1往后走}else //L2的节点值小{if (newhead == NULL) //新链表为空链表{newhead = newtail = L2;}else //新链表不为空链表{newtail->next = L2;//把L2放在新链表newtail = newtail->next;}L2 = L2->next;//L2往后走}}

跳出循环后有两种情况,要么L1先走到空,要么L2先走到空 ,上面的情况就是L1走到空,L2没有走到空,我们直接把L2拿下来尾插就行了,出while循环后的代码如下

    //跳出循环后if (L2) //L2没走到空newtail->next = L2;if (L1) //L1没走到空newtail->next = L1;
return newhead;

我们还需要处理一下空链表的情况,空链表的情况代码放在函数体最前面

	//空链表的情况if (list1 == NULL)return list2;if (list2 == NULL)return list1;

我们会发现代码重复的部分很多,我们如何去优化它?首先要找为什么会重复

我们向操作系统申请一个空间,这个空间不存有效数据

newhead = newtail = (ListNode*)malloc(sizeof(ListNode));

此时链表也改变了

 所以现在的代码就变了,返回值也变了

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{//空链表的情况if (list1 == NULL)return list2;if (list2 == NULL)return list1;ListNode* L1 = list1; //遍历原链表ListNode* L2 = list2;ListNode* newhead, * newtail; //创建新链表newhead = newtail = (ListNode*)malloc(sizeof(ListNode));while (L1 && L2)//循环遍历,有一个走到NULL就退出{if (L1->val < L2->val)//L1的节点值小{newtail->next = L1; //把L1放在新链表newtail = newtail->next;L1 = L1->next;//L1往后走}else //L2的节点值小{newtail->next = L2;//把L2放在新链表newtail = newtail->next;L2 = L2->next;//L2往后走}}//跳出循环后if (L2) //L2没走到空newtail->next = L2;if (L1) //L1没走到空newtail->next = L1;return newhead->next;
}

我们malloc的空间要记得释放,所以可以在return上面加三排代码,而且return的值也要改一下

    ListNode* ret = newhead->next;//存newhead->next的值free(newhead);newhead = NULL;return ret;

所以这里的头节点就是哨兵位

题目5:环形链表

我们先介绍一下约瑟夫问题的历史背景

据说著名犹太 历史学家 Josephus( 弗拉维奥·约瑟夫斯 )有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。约瑟夫将自己和他的朋友安排在16和31个位置,于是逃脱了这场死亡游戏

来看一下环形链表

我们画个图简单介绍一下

此时把3节点去掉,继续从下一个存在的节点开始报数

再把节点1去掉,继续从下一个存在的节点开始报数

把节点5去掉,现在留下两个节点,如果约瑟夫和他的朋友想要存活,就要站在2或4的位置

那么这道题就是类似,不过是留下一个节点

5个节点,报数为2举例

 

最后就留下了节点3

代码中我们如何分析呢?看下图。

数1之后数2,此时pcur后移,prev后移

pcur所指向的节点数到了2,要销毁pcur所指的节点,销毁的时候先让prev的next指针指向pcur的next

然后把pcur指向的节点销毁,并且让pcur指向下一个节点,也就是prev的next指针指向的位置

然后继续数数,从1开始

然后就是重复步骤,当只剩下两个节点时,分析一下

pcur报数为2,删掉5这个节点,要先让prev的next指针指向pcur的next,也就是节点3此时自己指向自己

然后再销毁节点5,此时pcur也指向节点3 

大概分析就是这样,我们代码实现一下

1.先创建带环链表

先写一个创建节点的函数

 typedef struct ListNode ListNode;ListNode* BuyNode(int x)//创建节点{ListNode* node=(ListNode*)malloc(sizeof(ListNode));if(node==NULL){exit(1);}node->val = x;node->next = NULL;return node;}

然后写带环链表的函数

 ListNode* CreatCircle(int n)//创建带环链表{ListNode* phead=BuyNode(1);//先创建头节点ListNode* ptail=phead;for (int i = 2; i <= n; i++){ptail->next=BuyNode(i);//尾插新节点ptail = ptail->next;}ptail->next=phead;//头尾相连,变成循环链表return ptail;}

然后在ysf函数中实现

int ysf(int n, int m )
{ListNode* prev = CreatCircle(n);//环形链表尾节点由prev接收ListNode* pcur = prev->next;//头节点由pcur接收int count = 1;while (pcur->next != pcur){if(count == m) //需要销毁节点{prev->next = pcur->next;free(pcur);pcur = prev->next;count = 1;//重新计数}else //不用销毁节点{prev = pcur;pcur = pcur->next;count++;}}return pcur->val;
}

 这次分享就到这里了,拜拜~

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

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

相关文章

美化博客文章(持续更新)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;游戏实现&#xff1a;贪吃蛇​​​​​​ &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 该文提供我的一些文章设计的一些方法 目录 1.应用超链接 1.应用超链接

mysql in查询优化

都说in查询比较慢&#xff0c;要改成子查询模式&#xff0c;ChatGPT大模型告诉了我&#xff0c;SQL中替换In查询的10种方法&#xff0c;太赞了&#xff0c;按照这个说的集中方法&#xff0c;验证一下。因为实际项目中确实存在in很多的情况。 查询执行的先后顺序对优化有必要&am…

【EI会议征稿】2024年先进机械电子、电气工程与自动化国际学术会议(ICAMEEA 2024)

2024 International Conference on Advanced Mechatronic, Electrical Engineering and Automation ●会议简介 2024年先进机械电子、电气工程与自动化国际学术会议&#xff08;ICAMEEA 2024&#xff09;将汇聚全球机械电子、电气工程与自动化领域的专家学者&#xff0c;共同…

计算机网络:CSMA/CA协议

计算机网络&#xff1a;CSMA/CA协议 CSMA/CA概述帧间间隔工作原理退避算法虚拟载波监听 CSMA/CA概述 讲解CSMA/CA之前&#xff0c;我们回顾一下CSMA/CD的三个特性&#xff1a; 多址接入MA&#xff1a;多个主机连接在一条总线上&#xff0c;竞争使用总线 载波监听CS&#xff1a…

web网站搭建实验

综合练习&#xff1a;请给openlab搭建web网站 网站需求&#xff1a; 1.基于域名www.openlab.com可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料 和缴费网站&#xff0c;基于&#xff0c;www.openlab.com/data网站…

X-314智能合约:金融创新的强大引擎

&#x1f4a5;火爆到烫手的X-314智能合约&#x1f525; X-314智能合约是基于以太坊区块链开发的&#xff0c;具有高度可定制性和灵活性。 ave开单独板块&#xff1b;详细资料已经准备好&#xff1b;对web3感兴趣的大佬货&#xff1b;多交流多指导&#x1f91d; ​X-314智能合…

JUC(java.util.concurrent) 的常见类

Callable 接口 Callable 的用法 Callable 是一个 interface&#xff08;类似之前的 Runnable&#xff0c;用来描述一个任务&#xff0c;但是没有返回值&#xff09;也是描述一个任务的&#xff0c;有返回值。方便程序猿借助多线程的方式计算结果. 例如&#xff1a;创建线程…

Zynq7000系列中PL时钟使用

可编程逻辑&#xff08;PL&#xff09;具有自己的时钟管理生成和分配功能&#xff0c;并从处理器系统&#xff08;PS&#xff09;中的时钟发生器接收四个时钟信号&#xff08;如图25-10所示&#xff09;。 在嵌入式系统中&#xff0c;PL时钟的管理和分配对于确保逻辑电路的正确…

QT跨平台读写Excel

QT跨平台读写Excel 背景Excel工具CMakeLists.txt工程目录 背景 开发框架QT&#xff0c;makefile构建工具CMake&#xff0c;编译器MinGW Excel工具 考虑跨平台则不能使用针对微软COM组件的QAxObject来读写Excel&#xff0c;因此使用开源QtXlsx。 这里是将QXlsx当做源码嵌入使…

使用FastDDS编译IDL文件

1.安装FastDDS环境 Ubuntu22.04 1.1安装依赖的软件 sudo apt-get update //基础工具安装 sudo apt install cmake g python3-pip wget git //Asio 是一个用于网络和低级 I/O 编程的跨平台C库&#xff0c;它提供了一致的 异步模型。 TinyXML2是一个简单&#xff0c;小巧&…

Redis进阶——GEO地理坐标附近商户案例

目录 GEO数据结构的基本用法GEO概述GEO命令的使用GEO数据结构练习 导入店铺数据到GEO业务场景实现代码如下 实现附近商户功能 GEO数据结构的基本用法 GEO概述 GEO就是Geolocation的简写形式&#xff0c;代表地理坐标。Redis在3.2版本中加入了对GEO的支持&#xff0c;允许存储…

过氧化氢滴定方法可用的PFA器皿有哪些?

滴定液:KMnO4标准溶液 试液:H2O2商品液(3%)&#xff0c;H2SO4 (3.0mol/L ) 指示剂:酚酞指示剂 仪器:分析天平&#xff0c;PFA酸式滴定管50mL&#xff0c;PFA 移液管10mL/25mL、PFA 容量瓶250mL、PFA锥形瓶250mL 1、KMnO4标准溶液浓度的标定(见实验:高锰酸钾标准溶液的配制与…

nodejs工具脚本json转excel

json转excel 主要使用 sheetjs 库 vim convertJsonToExcel.js 封装转换方法 import fs from fs; import XLSX from xlsx;/*** 扁平化嵌套json对象* param {Object} jsonObj* param {String} prefix* returns*/ export function flattenKeys(jsonObj, prefix ) {const resul…

java-springmvc 01

MVC就是和Tomcat有关。 01.MVC启动的第一步&#xff0c;启动Tomcat 02.Tomcat会解析web-inf的web.xml文件

战姬物语部署

一.准备环境 #关闭seliunx和防火墙 setenforce 0 systemctl stop firewalld systemctl disable firewalld #配置源&#xff0c;并安装常用工 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo curl -o /etc/yum.repos.d/epel.repo …

Spring Task 定时任务(含结合cron 表达式)

目录 一、Spring Task的介绍 二、使用方法 2.1 配置类启用定时任务支持&#xff1a; 2.2 同步定时任务 ​编辑2.3 fixedRate 可以看出不能满足我们的日常需求 那如何让其开启异步呢&#xff08;开启多个线程工作&#xff09; 三、Spring Task 结合cron表达式 3.1 corn 表…

【动态规划】dp 路径问题(不同路径、路径最小和、地下城游戏...)

文章目录 1. 前言 - 理解动态规划算法1.5 关于dp路径问题2. 例题2.1_不同路径Warning. 关于状态表示 3. 算法题3.1_不同路径II3.2_珠宝的最高价值3.3_下降路径最小和3.4_最小路径和3.5_地下城游戏关于状态表示的两种选法&#xff1a; 1. 前言 - 理解动态规划算法 关于 动态规划…

超越GPT-4V,苹果多模态大模型上新,神经形态计算加速MLLM(一)

4月8日&#xff0c;苹果发布了其最新的多模态大语言模型&#xff08;MLLM &#xff09;——Ferret-UI&#xff0c;能够更有效地理解和与屏幕信息进行交互&#xff0c;在所有基本UI任务上都超过了GPT-4V&#xff01; 苹果开发的多模态模型Ferret-UI增强了对屏幕的理解和交互&am…

做生意能只用电子名片吗?

做生意&#xff0c;收到纸质名片是不可避免的&#xff0c;电子名片不可能完全能代替纸质名片&#xff0c;如果想方便管理纸质名片的话&#xff0c;将名片拍照转为Excel是一种不错的方案&#xff0c;它可以让我们方便地通过表格筛选或搜索需要的信息&#xff0c;极大地提高了信息…

cdh cm界面HDFS爆红:不良 : 该 DataNode 当前有 1 个卷故障。 临界阈值:任意。(Linux磁盘修复)

一、表现 1.cm界面 报错卷故障 检查该节点&#xff0c;发现存储大小和其他节点不一致&#xff0c;少了一块物理磁盘 2.查看该磁盘 目录无法访问 dmesg检查发现错误 dmesg | grep error二、解决办法 移除挂载 umount /data10 #可以移除挂载盘&#xff0c;或者移除挂载目…