C语言数据结构基础—单链表相关数据结构题目6道

       上一篇博客中,我们大致的讲解了单链表相关的各种接口。接下来我们通过例题来运用类似的思想,达到学以致用的目的。

1.移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

   没有说明头结点是什么,默认就是第一个元素,而不是哨兵位。

思路一:

遍历链表,利用删除节点的操作,遇到节点就删除

 typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {ListNode* pcur=head;ListNode* prev=NULL;while(pcur){if(pcur->val==val){//SLTErase(pcur);if(prev){prev->next=pcur->next;free(pcur);pcur=prev->next;}else{//开头为空的情况head=head->next;pcur=head;}}else{prev=pcur;pcur=pcur->next;}}return head;
}

        整体思路不难,但具体操作有很多细节需要注意,比较繁琐:在遍历时,为了保证能让该节点的前后节点相连,必须要用一个prev比pcur一直慢走一步。同时,如果prev为空时,删除的操作还不一样。

思路二:

使用新的链表指针NewHead和NewTail

注意,此处并没有开辟新的空间,我们只是利用两个指针串联了所有符合要求的节点

将符合条件的5转移过来之后,5的后面看似是NULL,实在5的next指向的是6,需要在最后给ptail指向的节点的next赋值为NULL。又有可能出现空链表的特殊测试用例情况(用例2),所以我们再用if判断一下,保证newTail的next是存在的

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {ListNode* NewHead;ListNode* NewTail;//最终会走到尾巴,但其实际作用是遍历链表NewHead=NewTail=NULL;ListNode* pcur=head;while(pcur){if(pcur->val!=val){if(NewHead==NULL){//第一次NewTail=NewHead=pcur;}else{NewTail->next=pcur;NewTail=pcur;}}pcur=pcur->next;}if(NewTail){NewTail->next=NULL;}return NewHead;
}

2.链表的中间节点

876. 链表的中间结点 - 力扣(LeetCode)

非常简单的题目(计数一次之后直接找中点即可),但重点不是如何通过该题目,而是学习新思想:快慢指针

先定义两个指针 fast slow

核心思想就是:slow走一步,fast走两步   

不过,任然需要分类考虑偶数和奇数的不同情况:从头开始走,总数为偶数时fast走到末尾的NULL,总数为奇数时fast走到最后一个节点。

 typedef struct ListNode  ListNode;
struct ListNode* middleNode(struct ListNode* head) {ListNode* fast;ListNode* slow;fast=slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;}return slow;
}

!!注意:如果调换while里面的两个条件的顺序,将发生 执行错误 ,原因是当有偶数个时,我们最后fast会停在NULL的位置,无法通过NULL找到其next,所以先判断fast如果fast就已经不满足条件了,由于c语言短路的特性,将不再判断fast->next,从而避免出错。

3.反转链表

206. 反转链表 - 力扣(LeetCode)

思路一:遍历原链表结点,并且创建新链表,遍历时每个结点都进行头插到新链表

思路二:使用三个指针n1 n2 n3,并依次赋值NULL  head  head->next

分别记录:前驱节点 当前节点 后继节点

改变节点的指针指向,以此来达到反转的目的。具体流程如下:

     我们可以先直接让n2(节点一)的next指向n1(NULL),并且由于n3保存了节点2的地址所以不用担心丢失节点2,然后

n1=n2;
n2=n3;
n3=n3->next;

此时就变成了:节点一指向NULL,n1指向节点一,n2指向节点二,n3指向节点三,一轮循环结束,已经成功逆置了第一个元素,让他成为next指向NULL的目标链表的最后一个元素 。我们继续这个操作,仍然让n2指向n1,再

n1=n2;
n2=n3;
n3=n3->next;

.....................   依次循环

不过细心的你一定发现,我们再使用n3->next之前最好先判断n3是否为空,免得出现“执行错误”。

n1=n2;
n2=n3;
if(n3)
n3=n3->next;

直到最后一次时

最后一次的特征是:n2=n3;执行后,n2为空,这就成为了我们while循环里的条件。

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {ListNode* n1=NULL;ListNode* n2=head;if(head==NULL){return head;}ListNode* n3=head->next;while(n2){n2->next=n1;n1=n2;n2=n3;if(n3)n3=n3->next;}return n1;
}

4.合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

有多种多样的实现思路,比如将l2与l1比较,比较到合适位置就将l2的元素搬运到l1中并插入。

但是为了体现一种新思想:使用 哨兵位

我们此处只着重介绍一种思路:创建新链表

大致思路就是,先创建两个节点newHead newTail 指针,再用l1 l2两个链表指针分别遍历两个链表,分别比较l1 l2的数据,谁小就把谁放进新链表,当任意一个指针指向空(已经跑出了链表时),就结束循环,将指向非空的指针所指向的剩下的内容(最小的一些数据),接到新链表中。

     面对这种ONLINE JUDGE题目,我们不难发现。他会给一些抽象的特殊测试用例,如此题中的case2 3

就像高中时数学大题做不出来就先做特殊情况蹭分数的想法一样,先写出两个特殊情况:

然后完成我们的逻辑

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {//用两个变量代替,避免最后找不到list1和list2ListNode* l1=list1;ListNode* l2=list2;ListNode* newHead=NULL;ListNode* newTail=NULL;if(l1==NULL){return list2;}if(l2==NULL){return list1;}while(l1&&l2){if(l1->val<l2->val){if(newHead==NULL){newHead=newTail=l1;}else{newTail->next=l1;newTail=newTail->next;}l1=l1->next;}else{if(newHead==NULL){newHead=newTail=l2;}else{newTail->next=l2;newTail=newTail->next;}l2=l2->next;}}//退出循环,此时应该有一个指针指向空,我们将剩下的一个指向的小数据们接在新链表末尾if(l1){newTail->next=l1;}if(l2){newTail->next=l2;}return newHead;
}

细心的朋友会发现,此时我们并没有用到我们的哨兵位:

无论l1->val和l2->val的大小,我们都必须先判断newHead和newTail是否为空(两个判断一个即可),冗杂,这就体现哨兵位的优点了:

我们在newHead指向的新链表的第一个元素插入一个哨兵位,里面不放元素或者放置无效元素,避免检查新指针是否为空的这一步,优化代码

也就是将newHead newTail赋值为NULL的两步修改为

 ListNode* newHead=(ListNode*)malloc(sizeof(struct ListNode));ListNode* newTail=newHead;

有效减少while中的内容 

最后由于哨兵位(newHead)不再使用,为了避免出函数后内存泄漏,应当执行free(不需要置NULL,因为出函数后该变量自动就销毁了)

5.分割链表

面试题 02.04. 分割链表 - 力扣(LeetCode)

思路一:创建一个新链表,小于x的头插,大于或等于x的尾差

思路二:大小链表法

(大小链表分别定义一个头一个尾,lessHead和greaterHead分别作为哨兵位)

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x){if(head==NULL){return head;}ListNode* pcur=head;ListNode* lessTail,*lessHead;ListNode* greaterHead,*greaterTail;//将哨兵位赋值给大小链表lessTail=lessHead=(ListNode*)malloc(sizeof(struct ListNode));greaterHead=greaterTail=(ListNode*)malloc(sizeof(struct ListNode));while(pcur){if(pcur->val>=x){greaterTail->next=pcur;greaterTail=greaterTail->next;}else{lessTail->next=pcur;lessTail=lessTail->next;}pcur=pcur->next;}//链接大小链表,注意给末尾赋NULLgreaterTail->next=NULL;lessTail->next=greaterHead->next;ListNode* ret=lessHead->next;free(lessHead);free(greaterHead);return ret;
}

注意:当我们把原本不在最后一个位置的节点放在新链表的最后一个节点的时候,一定注意其next是不是指向空,否则就可能出现死循环等错误。

并且链接大小链表的两个句子不能交换,否则greaterTail和greaterHead若为空,就又会发生执行错误。

6.著名的约瑟夫问题

环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com)

据说著名犹太 Josephus有过以下的故事:在罗⻢⼈占领乔塔帕特后,39 个犹太⼈与
Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被⼈抓到,于是决定了⼀个⾃杀 ⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀,然后再由下⼀ 个重新报数,直到所有⼈都⾃杀⾝亡为⽌。 然⽽Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在 第16个与第31个位置,于是逃过了这场死亡游戏。
既然是删除中间特定下标的元素,是不是数组(顺序表)更方便使用呢?
当然不是的,中间涉及到删除和成环操作,链表更合适
首先根据n值,创建单向循环链表
ListNode* SLTBuyNode(int x){//创建单个的节点ListNode* p=(ListNode*)malloc(sizeof(ListNode));p->val=x;p->next=NULL;return p;}ListNode* SLTCreat(int n){ListNode* phead=SLTBuyNode(1);ListNode* ptail=phead;for(int i=2;i<=n;i++){ptail->next=SLTBuyNode(i);ptail=ptail->next;}//让首尾相连ptail->next=phead;return ptail;//因为后继需要执行删除操作,需要前驱结点prev,//所有我们直接返回尾结点来作为prev}

注意:这道题貌似没有定义节点的结构体,但是其实已经定义了,输入LIstNode的时候有一样的名字出现

其次:为什么要返回ptail? 先看主程序代码

int ysf(int n, int m ) {ListNode* prev=SLTCreat(n);ListNode* pcur=prev->next;
//开始遍历for(int count=1;pcur->next!=pcur;){if(count==m){//删除该节点,并且count回到1,做好前后相连工作count=1;prev->next=pcur->next;free(pcur);pcur=prev->next;}else{//向后移动count++;prev=pcur;pcur=pcur->next;}}return pcur->val;
}

正如前文所说,因为需要prev指针来记录前驱节点,在m=1的情况,我们如果返回头结点给phead,正常情况就会给ptail赋值为NULL,那么就无法执行prev->next的语句。

总的来说,为了避免节点指针为空又使用他们找自己的元素,出现执行出错的情况,我们应该合理使用哨兵位(如第4、5题)和循环链表(如此题)的特点

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

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

相关文章

python_可视化_交互_多条线段点击高亮显示

需求 使用matplotlib 绘制折线图 响应鼠标事件 单击折线 线条高亮显示 解决方法: 使用 mplcursors 库, 一句代码可实现. 代码 import matplotlib.pyplot as plt import mplcursors import numpy as np# 生成一些示例数据 x np.linspace(0, 10, 100) y np.sin(x)# 创建绘图…

Python复合型数据避坑指南

目录 前言 列表&#xff08;Lists&#xff09; 1. 修改可变对象 2. 浅拷贝和深拷贝 元组&#xff08;Tuples&#xff09; 集合&#xff08;Sets&#xff09; 字典&#xff08;Dictionaries&#xff09; 1. 键值唯一性 2. 键的类型 实际应用场景 1. 数据分析与清洗 2. 网络…

记忆化搜索(Function,天下第一)

Function 这是一道很直观的递归题目&#xff0c;但是使用递归会导致时间超限&#xff0c;所以需要使用记忆化搜素。 首先把坑点讲一下&#xff1a;出题人会给出负数&#xff0c;而我们知道数组下标是不能有负数的&#xff0c;如果是二维数组还可以用map数组进行储存&#xff0c…

DETR(1):论文详解

文章目录 1. DETR 模型结构2.损失函数2.1 预测结果和GT 的匹配2.2 训练的loss计算3.实验3.1 大物体表现效果好3.2 Transformer Encoder 和Decoder的作用3.3 object query4. 伪代码5. 结论

Python set 集合

Python 集合 集合&#xff08;set&#xff09;是一个无序的不重复元素序列。 集合中的元素不会重复&#xff0c;并且可以进行交集、并集、差集等常见的集合操作。 可以使用大括号 { } 创建集合&#xff0c;元素之间用逗号 , 分隔&#xff0c; 或者也可以使用 set() 函数创建…

width:100%和width:auto有啥区别

项目中使用了with属性&#xff0c;突然好奇auto 和 100% 的区别&#xff0c;特地搜索实践总结了一下观点 一、 width属性介绍二、 代码带入三、 分析比较四、 总结 一、 width属性介绍 width 属性用于设置元素的宽度。width 默认设置内容区域的宽度&#xff0c;但如果 box-siz…

皇冠测评:网络电视盒子哪个品牌好?电视盒子排行榜

欢迎各位来到我们的测评频道&#xff0c;本期我们要分享的产品是电视盒子&#xff0c;因很多网友留言不知道网络电视盒子哪个品牌好&#xff0c;我们通过为期一个月的测评后整理了电视盒子排行榜&#xff0c;想买电视盒子的可以看看下面这五款产品&#xff0c;它们各方面表现非…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第12章 项目质量管理(三)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

污水处理设备远程监控:HiWoo Cloud如何打造智慧环保新时代

随着工业化和城市化的快速推进&#xff0c;污水处理成为了保护环境、维护生态平衡的重要一环。传统的污水处理设备管理方式往往依赖于人工巡检和现场控制&#xff0c;不仅效率低下&#xff0c;而且难以实时掌握设备的运行状况。在这个背景下&#xff0c;HiWoo Cloud平台推出了污…

Matlab: Introduction to Hybrid Beamforming

文章目录 来源混合波束赋形的基本概念System Setup关键函数 来源 在matlab的命令行输入 doc hybrid beamforming 混合波束赋形的基本概念 混合波束形成简介 本例介绍了混合波束形成的基本概念&#xff0c;并说明了如何模拟这种系统。 现代无线通信系统使用空间复用来提高散…

模拟算法题练习(一)

模拟算法介绍&#xff1a; 模拟算法通过模拟实际情况来解决问题&#xff0c;一般容易理解但是实现起来比较复杂&#xff0c;有很多需要注意的细节&#xff0c;或者是一些所谓很“麻模“的东西。 模拟题一般不涉及太难的算法&#xff0c;一般就是由较多的简单但是不好处理的部…

如何开发自己的npm包并上传到npm官网可以下载

目录 搭建文件结构 开始编写 发布到npm 如何下载我们发布的npm包 搭建文件结构 先创建新文件夹,按照下面的样子布局 .├── README.md //说明文档 ├── index.js //主入口 ├── lib //功能文件 └── tests //测试用例 然后再此根目录下初始化package包 npm init…