【数据结构】猛猛干11道链表OJ(未完待续ing)

前言知识点

链表的调试技巧

int main()
{struct ListNode* n1=(struct ListNode*)malloc(sizeof(struct ListNode));assert(n1);struct ListNode* n2=(struct ListNode*)malloc(sizeof(struct ListNode));assert(n2);struct ListNode* n3=(struct ListNode*)malloc(sizeof(struct ListNode));assert(n3);struct ListNode* n4=(struct ListNode*)malloc(sizeof(struct ListNode));assert(n4);struct ListNode* n5=(struct ListNode*)malloc(sizeof(struct ListNode));assert(n5);n1->next=n2;n2->next=n3;n3->next=n4;n4->next=n5;n5->next=NULL;n1->val=1;n2->val=2;n3->val=3;n4->val=2;n5->val=1;while(newhead!=NULL){cout << newhead->val << "->" ;newhead=newhead->next;}cout << "NULL" << endl;return 0;
}

手搓一个链表出来,方便观察我们的调试与结果

一、移除链表元素(俩个方法)

1.1题目链接

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

1.2题目描述

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

1.3解题思路

思路1

遍历链表,比对每一个节点的数据与val是否相等,如果相等,就free掉该节点。

时间复杂度:O(N) ;空间复杂度:O(1)

 

易错点(1.如果head->val就是val,那么head得更新  2.小心free的时候找不到next)

1、当链表的头结点的数据等于val时,我们free掉该节点后需要挪动head指针,让其指向新的头结点;

2、我们在遍历链表的时候需要记录前一个节点的地址,因为当我们free掉当前节点之后,我们要让前一个节点的next;链接到当前节点的下一个节点;

struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* cur =head;struct ListNode* prev=NULL;//不用担心prev的节点更新问题while(cur){if(cur->val==val){if(cur==head)//头就得动手术,更新头节点{head=cur->next;free(cur);cur=head;}else{prev->next=cur->next;//意志遗传 prev越过6指向3free(cur);cur=prev->next;//cur到了3这个位置,还是比prev快一步}}else{prev=cur;//保证当没有节点删除的时候,起码prev在cur上cur=cur->next;//先把prev带到这里,然后自己又偷偷走一步,保证相对位置}}return head;
}

思路2

遍历链表,将不等于val的节点尾插到一个新的链表,将等于val的节点free掉。

时间复杂度:O(N) 空间复杂度:O(1)

难就难在我想不到用尾插,而是一位的改变指针指向,这令我非常晕乎乎 

易错点

1、由于我们是把原链表中的节点尾插到新链表中去,所以我们插入元素的时候需要判断链表是否为空,如果为空,我们需要改变新链表的头结点;(这样的尾插并不方便)

2、当然,我们也可以把我们的新链表设计为带哨兵位的,这样我们直接进行尾插就行,但是要注意我们返回的应该是guard->next,因为哨兵位头结点不用于存储数据,同时在return之前记得把哨兵位头结点释放掉;

但要小心: newhead->next不要随便给别人赋值,这是一个随机值!!!(等到下面的反转链表就可以看到异常了)

 

 

3、由于原链表中最后一个节点的数据可能等于val,所以我们需要将新链表中尾结点的next置为NULL,防止通过它来访问已经被释放掉的节点。(这样的藕断丝连是会出问题的)

从代码逻辑中看出:最后一个是6的时候,cur=cur->next,也即是说把6直接给忽略掉了,并没有有效的删除,但是5->next还是指着6节点位置

struct ListNode* removeElements(struct ListNode* head, int val) {if(head==NULL){return NULL;}struct ListNode* cur =head;struct ListNode* prev=NULL;//不用担心prev的节点更新问题//因为要尾插,不想用tail了,定义一个Guardstruct ListNode* newhead=(struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* tail=newhead;while(cur){if(cur->val!=val){tail->next=cur;//更新尾节点链接//newhead->next=cur; 头节点别动!!!因为这个题不像反转链表,反转链表的newhead往前更新,因为箭头的指向在改变,所以头的改变到时候到了末尾正好是正确的tail=cur;//更新尾节点位置cur=cur->next;}else{cur=cur->next;// struct ListNode* next = cur->next;// free(cur);// cur = next;//杭哥这样删除是为了防止内存泄漏,但我直接往下一步走了}}tail->next=NULL;//这句是一个大bughead=newhead->next;free(newhead);return head;
}

(我这里cur节点偷了点懒,没有真正的free节点,这样会造成内存泄漏,虽然AC了,但是会有大问题,所以还是得多加小心!) 


二、反转链表(俩个方法)

2.1题目链接

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

2.2题目描述

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

2.3思路分析

思路1(利用迭代的思路)

指针的定义刚开始就是很有讲究的:

所以我们将n3定义在循环中,这也是我们常用的套路 

n2负责改变指针指向,n3负责前进并且记录位置防止丢失,n1是用来告知n2指向哪里

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

思路2

将原链表中的节点头插到新链表中,然后返回新链表的头。(因为一直头插就可以改变链表顺序)

时间复杂度:O(N) 空间复杂度:O(1)

 但是头插也有易错点:

1.记得更新头

2.记得定义一个next,要不然cur回不到原来的位置

3.还要小心空指针解引用的问题

 

 

//法二:取出链表的每一个节点头插
struct ListNode* reverseList(struct ListNode* head){struct ListNode* newhead = NULL;struct ListNode* cur = head;while(cur){struct ListNode* next = cur->next;cur->next = newhead;newhead = cur;cur = next;}return newhead;
}

三、 链表的中间节点(如果只让遍历一次,怎么办?)

3.1题目链接

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

3.2题目描述

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。时间复杂度:O(N) 空间复杂度:O(1)

3.3思路分析

思路1

遍历两遍数组,第一遍求出链表长度,第二步找出链表的中间节点并返回。

代码实现

//法一:遍历两次链表,第一次找出链表有几个节点,第二次返回链表的中间节点
struct ListNode* middleNode(struct ListNode* head){struct ListNode* cur = head;int count = 0;while(cur){count++;cur = cur->next;}cur = head;count /= 2;while(count--){cur = cur->next;}return cur;
}

思路2 

由于这道题用第一种方法实现十分简单,所以在面试中面试官会加一个限制条件:要求只能遍历一遍链表;这时候就只能用快慢指针来解题了;

快慢指针:定义两个指针 – fast slow,慢指针一次走一步,快指针一次走两步;当链表长度为奇数,fast->next == NULL时,slow 为中间节点;当链表长度为偶数,fast == NULL 时,slow 为中间节点。时间复杂度:O(N) 空间复杂度:O(1)

快慢指针对于寻找节点是一把好手,我们一定要合理运用这种思维逻辑!

但是我们也还是得画出奇数偶数的情况

 

可以看出,对于奇数和偶数我们的fast指针一个在末尾,一个在NULL,所以只要我们while(fast && fast->next==NULL) 这样控制一下就可以兼容俩种情况啦~ 一定要多画图!!

 

 易错点

我们在写while循环的条件时,必须写成 fast && fast->next,不能写成 fast->next && fast,因为当链表长度为偶数时,后面这种写法会发生空指针的解引用。

而且还有我们的while循环内部的逻辑是 && 我刚开始理解的是 || 但这样是不对的

//法二:使用快慢指针,slow一次走一步,fast一次走两步,只遍历一遍数组
//奇数个节点时,当fast->next == NULL时,slow刚好到达中间节点
//偶数个节点时,当fast == NULL时,slow刚好达到中间节点
struct ListNode* middleNode(struct ListNode* head){struct ListNode* fast, *slow;slow = fast = head;//注意:while条件中fast一定要写前面,不然偶数个时fast->next会造成空指针解引用while(fast && fast->next)  //节点是奇数还是偶数未知注意:{slow = slow->next;fast = fast->next->next;}return slow;
}

 四、链表中倒数第K个节点

4.1题目链接

链表中倒数第k个结点LEETCODE

4.2题目描述

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

4.3思路分析

看到求节点问题,脑子里立马想到快慢指针法

但很明显的是,对于链表而言,往回找是非常困难的,所以就让fast走的时候,让slow也走,保持二者的相对距离

 

 

int kthToLast(struct ListNode* head, int k){struct ListNode* slow=head;struct ListNode* fast=head;if(head->next==NULL){return head->val;}while(k--){fast=fast->next;}while(fast){slow=slow->next;fast=fast->next;//这里会出现空指针的解引用!!!}return slow->val;
}

 这个题难就难在最后的特殊情况得特判一下,因为fast=fast->next,当只有一个节点的时候,会出现空指针的解引用

4.4心得


五、六、七、八、九、十、十一...(今天感冒了,未完待续)


总结:链表做题套路

为了避免空指针被解引用的问题,我们一般在循环中创建next变量

看到求中间节点或者第几个节点或者倒数第几个节点就用快慢指针

对于需要对链表进行操作的,我们除了可以在原先链表中建立指针,我们还可以新开辟一个头节点,使得我们可以进行尾插和头插(尾插不改变元素顺序,头插改变元素顺序)

关于哨兵位带头的问题(要尾删,必建头)

我们必须认识到一个问题:单链表就适合头插头删,不适合尾插尾删

带头对于头插也有好处:这样可以避免重复的更新头节点,很方便嘞!(但是杭哥建议头插不带哨兵头也可以,但是得记得更新)

对于尾插的好处:省略特殊情况的判空

 

 

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

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

相关文章

蓝桥杯 2023 省B 飞机降落

首先&#xff0c;这题要求的数据量比较少&#xff0c;我们可以考虑考虑暴力解法。 这题可能难在很多情况的考虑&#xff0c;比如说&#xff1a; 现在时间是10&#xff0c;有个飞机20才到&#xff0c;我们是可以干等10分钟。 #include <iostream> #include <…

对话奇酷网络董事长吴渔夫: 迟到的游戏公司会被AI浪潮卷入海底

“ 迟到的游戏公司会被无形的 AI 浪潮卷入海底。” 整理 | 梦婕 编辑 | 云舒 出品&#xff5c;极新 2024年3月4日&#xff0c;在极新与吴渔夫的对话中&#xff0c;吴渔夫多次呼吁“全力拥抱AI”。在这场AI浪潮中&#xff0c;作为中国网游的先锋&#xff0c;他带着 25 年“中…

【web前端】<meta>标签

meta元素可以提供有关页面的元信息&#xff08;meta-information&#xff09; meta标签位于文档的头部&#xff0c;是空元素 meta元素的属性 属性值描述http-equiv expires refresh X-UA-compatible 定义HTTP协议的头部元信息名称。其中&#xff0c;expires设置网页在缓存区的…

记录一下目前为止的算法成长

每日笔记 复习曲线 间隔1天、3天、7天、15天、30天&#xff0c;然后以一个月为周期复习 2023. 12. 24 一定要每天早中晚都要复习一下 早中午每段一两道, 而且一定要是同一个类型, 不然刷起来都没有意义 11.29 开始向着面试刷题跟进! 每天刷4题左右 ,一周之内一定要是统一类…

笔记本8代i5和台式机12代i5的性能比较

一、 台式机12代i5 二、笔记本8代i5 在多核性能上差不多是2.4倍&#xff0c;所以跑大一点的Matlab或者别的程序&#xff0c;用台式机&#xff0c;后边实验室能用上超多核服务器另说。

uniapp,导航栏(切换项)有多项,溢出采取左滑右滑的形式展示

一、实现效果 当有多项的导航&#xff0c;或者说切换项&#xff0c;超出页面的宽度&#xff0c;我们采取可滑动的方式比较好一些&#xff01;并且在页面右边加个遮罩&#xff0c;模拟最右边有渐变效果&#xff01; 二、实现代码 html代码&#xff1a; <!-- 头部导航栏 --…

鸿蒙Harmony应用开发—ArkTS-转场动画(共享元素转场)

当路由进行切换时&#xff0c;可以通过设置组件的 sharedTransition 属性将该元素标记为共享元素并设置对应的共享元素转场动效。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 属性 名称参数参数描述…

F. Microcycle(dfs 搜寻路径 + 并查集)

解析&#xff1a; 本题的意思是&#xff0c;求一个环的最小的那条边。 并且输出其这个环的点。 我们可以利用并查集&#xff0c;进行确定其是否有环路。在将所用的边从大到小排序。 利用 vector容器&#xff0c;pop_back() 和 push的特性。 起点为 u终点为 v寻找路径。 代…

投简历没回复?9位DBA公众号集结,快上车!

&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61c;&#x1f61c; 中国DBA联盟(ACD…

寄快递很麻烦怎么办?无脑方法教会你便宜寄快递!快冲!

现在我们每天都会去寄快递&#xff0c;不仅寄大件还会发物流&#xff0c;但是我们真的了解快递的价格吗&#xff1f;寄快递必须拿到快递驿站吗&#xff1f;去菜鸟驿站寄快递会给我们便宜吗&#xff1f;有没有什么便宜的寄快递的方法呢&#xff1f;驿站会有包装快递的包装袋吗&a…

Wireshare捕获接口中没有本地连接

1. 查看npf服务是否启动 服务名无效&#xff0c;需要安转WinPcap 2. 勾选Npcap Packet Driver (NPCAP) 3. 重新启动Wireshark 重新启动Wireshark后&#xff0c;本地连接有了

SpringCloud从入门到精通速成(一)

文章目录 1.认识微服务1.0.学习目标1.1.单体架构1.2.分布式架构1.3.微服务1.4.SpringCloud1.5.总结 2.服务拆分和远程调用2.1.服务拆分原则2.2.服务拆分示例2.2.1.导入Sql语句2.2.2.导入demo工程 2.3.实现远程调用案例2.3.1.案例需求&#xff1a;2.3.2.注册RestTemplate2.3.3.实…

学生信息管理系统--修改信息(非常详细的修改,更新,撤销,删除逻辑)

目录 概述修改包括的操作修改在每个模块中的应用 详解修改与更新取消删除 特殊概念数据集游标 总结 概述 学生信息管理系统&#xff0c;功能相对简单且代码重复性高&#xff0c;应该采用复用的思想来减少代码的冗余和提高代码的可维护性。然而&#xff0c;对于基础入门项目来说…

NVM使用教程

文章目录 ⭐️写在前面的话⭐️1、卸载已经安装的node2、卸载nvm3、安装nvm4、配置路径以及下载源5、使用nvm下载node6、nvm常用命令7、全局安装npm、cnpm8、使用淘宝镜像cnpm9、配置全局的node仓库&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680;&#…

Word2vec学习笔记

&#xff08;1&#xff09;NNLM模型&#xff08;神经网络语言模型&#xff09; 语言模型是一个单纯的、统一的、抽象的形式系统&#xff0c;语言客观事实经过语言模型的描述&#xff0c;比较适合于电子计算机进行自动处理&#xff0c;因而语言模型对于自然语言的信息处理具有重…

MySQL学习八:窗口函数(一)

目录 一、窗口函数1. 窗口函数定义2. 窗口函数语法3. 演示表格一4. 窗口的确定4.1 例1&#xff1a;查询各班级总分4.2 例2&#xff1a;查询各班级累计总分4.3 分区子句&#xff08;partition by&#xff09;4.4 排序子句&#xff08;order by&#xff09;4.5 窗口子句&#xff…

单片机-- 数电(3)

编码器与译码器 译码 &#xff1a;将二进制代码转化为其他进制的代码 编码 &#xff1a;就是将其他代码转换为二进制码 编码器的类型 1二进制编码器 用n位二进制数码对2的n次方个输入信号进行编码的电路 2二-十进制编码器 将0到9十个十进制数转化为二进制代码的电路 2…

crossover虚拟机 crossover软件干嘛的 虚拟机软件的使用方法 mac虚拟机装windows

与传统的虚拟机软件&#xff08;如VMware、VirtualBox&#xff09;相比&#xff0c;CrossOver具有更高的运行效率和更好的用户体验。因为它并不创建一个完整的Windows虚拟机&#xff0c;而是仅模拟应用程序所需的运行环境。这使得CrossOver在启动和运行Windows应用程序时更加快…

手撕HashMap底层源码(学习内容全)

day28上 集合框架 标绿已经学习底层&#xff0c;深入底层主要是研究实现类底层 手撕HashMap底层源码 JDK1.7版本的HashMap为例&#xff08;注意实验代码时进行版本切换&#xff09; 代码注释参考理解 //day27初识 public class HashMap<K,V> extends AbstractMap<K,…

SpringBoot3整合Mybatis-Plus与PageHelper包冲突解决

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; SpringBoot3整合Mybatis-Plus与PageHelper包冲突解决 ⏱️ 创作时间&a…