【数据结构】猛猛干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.rhkb.cn/news/280144.html

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

相关文章

JVM学习-类加载

目录 1.类文件结构 2.类加载器 3.类加载的三个阶段 3.1加载 3.2链接 3.2.1验证 3.2.2准备阶段 3.2.3解析阶段 3.3初始化 4.拓展&#xff1a;反射 4.1获取类对象 4.2创建实例 4.3获取方法 4.4方法调用 1.类文件结构 2.类加载器 类加载器用来将类文件的二进制字节码加载到JV…

蓝桥杯之简单数论冲刺

文章目录 取模快速幂 取模 这道题目有两个注意点&#xff1a; 1.当你的取模之后刚好等于0的话&#xff0c;后面就不用进行后面的计算 2.if sum detail[i] > q: 这个语句的等号也很重要 import os import sys# 请在此输入您的代码a,b,n map(int,input().split())week a*5 …

代码随想录刷题day29|非递减子序列全排列全排列II

文章目录 day29学习内容一、非递减子序列1.1、代码-错误写法1.1.1 多了一个return语句。1.1.2、nums[i-1] > nums[i]&#xff0c;这个条件写错了&#xff0c;为什么呢&#xff1f;1. 忽略了回溯算法的动态决策过程2. 限制了可能的递增子序列的探索 1.2、代码-正确写法 二、全…

还是了解下吧,大语言模型调研汇总

大语言模型调研汇总 一. Basic Language ModelT5GPT-3LaMDAJurassic-1MT-NLGGopherChinchillaPaLMU-PaLMOPTLLaMABLOOMGLM-130BERNIE 3.0 Titan 二. Instruction-Finetuned Language ModelT0FLANFlan-LMBLOOMZ & mT0GPT-3.5ChatGPTGPT-4AlpacaChatGLMERNIE BotBard 自从Cha…

wayland(xdg_wm_base) + egl + opengles 渲染使用纹理贴图的旋转 3D 立方体实例(十三)

文章目录 前言一、使用 stb_image 库加载纹理图片1. 获取 stb_image.h 头文件2. 使用 stb_image.h 中的相关接口加载纹理图片3. 纹理图片——cordeBouee4.jpg二、渲染使用纹理贴图的旋转 3D 立方体1. egl_wayland_texture_cube.c2. Matrix.h 和 Matrix.c3. xdg-shell-client-pr…

浅谈大模型“幻觉”问题

大模型的幻觉大概来源于算法对于数据处理的混乱&#xff0c;它不像人类一样可以by the book&#xff0c;它没有一个权威的对照数据源。 什么是大模型幻觉 大模型的幻觉&#xff08;Hallucination&#xff09;是指当人工智能模型生成的内容与提供的源内容不符或没有意义的现象。…

【JavaScript】JavaScript 程序流程控制 ② ( 循环流程控制 | 循环要素 - 循环体 / 循环终止条件 | for 循环语法结构 )

文章目录 一、JavaScript 程序流程控制 - 循环流程控制1、循环流程控制2、循环要素 - 循环体 / 循环终止条件3、for 循环语法结构 - 循环控制变量 / 循环终止条件 / 操作表达式4、for 循环 完整代码示例 一、JavaScript 程序流程控制 - 循环流程控制 1、循环流程控制 在 程序开…

C# 连接neo4j数据库,包括非默认的neo4j默认库

官方文档没找见&#xff0c;自己在源码里面找到的 private string _dbHost "bolt://localhost:7687"; private string _dbUser "neo4j"; private string _dbPassword "******"; private IDriver? _driver;public CQLOperation(string _data…

CTF-reverse-每日练题-xxxorrr

题目链接 https://adworld.xctf.org.cn/challenges/list 题目详情 xxxorrr ​ 解题报告 下载得到的文件使用ida64分析&#xff0c;如果报错就换ida32&#xff0c;得到分析结果&#xff0c;有main函数就先看main main函数分析 v6 main函数中&#xff0c;v6的值是__readfsqwor…

Haproxy 负载均衡集群

一. Haproxy &#xff1a; 1. Haproxy 介绍&#xff1a; HAProxy 是法国开发者威利塔罗 (Willy Tarreau) 在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障…

河南大学大数据平台技术实验报告二

大数据平台技术课程实验报告 实验二&#xff1a;HDFS操作实践 姓名&#xff1a;杨馥瑞 学号&#xff1a;2212080042 专业&#xff1a;数据科学与大数据技术 年级&#xff1a;2022级 主讲教师&#xff1a;林英豪 实验时间&#xff1a;2024年3月15日3点 至 2024年3月15日4点40 …

【矩阵】54. 螺旋矩阵【中等】

螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 解题思路 1、模拟顺时针螺旋顺序遍历矩阵…

完美解决 RabbitMQ可视化界面Overview不显示折线图和队列不显示Messages

问题场景&#xff1a; 今天使用docker部署了一个RabbitMQ&#xff0c;浏览器打开15672可视化页面发送消息后不显示Overview中的折线图&#xff0c;还有队列中的Messages&#xff0c;因为我要看队列中的消息数量。 解决方案&#xff1a; 进入容器内部 docker exec -it 容器id…

视频素材库app推荐的地方在哪里找?

视频素材库app推荐的地方在哪里&#xff1f;这是很多短视频创作者都会遇到的问题。别着急&#xff0c;今天我就来给大家介绍几个视频素材库app推荐的网站&#xff0c;让你的视频创作更加轻松有趣&#xff01; 蛙学网&#xff1a;视频素材库app推荐的首选当然是蛙学网啦&#xf…

CommonJs规范

文章目录 1. CommonJS 模块的导出2. CommonJS 模块的导入2.1使用 require 函数导入文件模块&#xff08;用户自定义&#xff09;2.2使用 require 函数导入核心模块&#xff08;Node.js 内置的模块&#xff09;2.3文件夹作为模块2.4模块的原理 在node中&#xff0c;默认支持的模…

GPT-4引领AI新纪元,Claude3、Gemini、Sora能否跟上步伐?

【最新增加Claude3、Gemini、Sora、GPTs讲解及AI领域中的集中大模型的最新技术】 2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚…

C语言——自定义类型——结构体(从零到一的跨越)

目录 前言 1.什么是结构体 2.结构体类型的声明 2.1结构体的声明 2.2结构体的创建和初始化 2.3结构成员访问操作符 2.3.1结构体成员直接访问 2.3.2结构体成员的间接访问 2.4结构体变量的重命名 2.5结构体的特殊声明 2.6结构的自引用 3.结构体内存对齐 3.1对齐规则 3…

JMH微基准测试框架学习笔记

一、简介 JMH&#xff08;Java Microbenchmark Harness&#xff09;是一个用于编写、构建和运行Java微基准测试的框架。它提供了丰富的注解和工具&#xff0c;用于精确控制测试的执行和结果测量&#xff0c;从而帮助我们深入了解代码的性能特性。 二、案例实战 在你的pom文件…

数据结构从入门到精通——直接选择排序

直接选择排序 前言一、选择排序的基本思想&#xff1a;二、直接选择排序三、直接选择排序的特性总结&#xff1a;四、直接选择排序的动画展示五、直接选择排序的代码展示test.c 六、直接选择排序的优化test.c 前言 直接选择排序是一种简单的排序算法。它的工作原理是每一次从未…

Hadoop大数据应用:HDFS 集群节点缩容

目录 一、实验 1.环境 2.HDFS 集群节点缩容 二、问题 1.数据迁移有哪些状态 2.数据迁移失败 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构软件版本IP备注hadoop NameNode &#xff08;已部署&#xff09; SecondaryNameNode &#xff08;已部署…