【leetcode 力扣刷题】链表基础知识 基础操作
链表基础知识 基础操作
- 链表基础操作
- 链表基础知识
- 插入节点
- 删除节点
- 查找节点
- 707. 设计链表
- 实现:单向链表:
- 实现:双向链表
链表基础操作
链表基础知识
在数据结构的学习过程中,我们知道线性表【一种数据组织、在内存中存储的形式】是线性结构的,其中线性表包括顺序表和链表。数组就是顺序表,其各个元素在内存中是连续存储的。
链表则是由数据域和指针域组成的结构体构成的,数据域是一个节点的数据,指针域存储下一个节点的地址,下一个节点依靠上一个节点的指针域寻址。给出整个链表的头节点地址(指针)head后,就能依次访问链表的每个节点。
链表定义:
链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该节点还有一个元素和一个指向另一条链表的引用。
根据链表间节点的指向,链表可以分为以下三种:
- 单向链表:指针域中只有指向下一个节点的地址;只能单向遍历链表;

- 双向链表:指针域中有两个指针,一个指向前一个节点,一个指向下一个节点;可以双向遍历链表;

- 循环链表:链表形成环,最后一个节点的指针域不赋值为null,而是指向头节点head;

定义一个链表节点,是单向的链表:
//定义一个结构体ListNode表示链表节点struct ListNode {int val; //数据域,这里用的int,根据实际情况选择type,比如char、float等ListNode *next; //指针域,存下一个节点的地址,所以是指针类型,并且下一个节点也是该结构体,所以是ListNode*//自定义构造函数,给数据域和指针域赋值ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}};
链表中头节点直接用head指针访问,其他节点用currentNode->next的指针访问,可见链表的头节点是特殊的。在插入、删除操作中,针对头节点都需要做特殊的处理,会较麻烦,因此在头节点前增加一个虚拟头节点【也叫附加头节点、哨兵节点等】dummy_head,虚拟头节点的指针域存head,即dummy_head->next = head;数据域无效,因为后续不会用到。给定dummy_head后,其他节点都用当前节点的next指针访问,即currentNode->next:

接下来介绍对链表进行的一些操作【穿针引线法】:
插入节点
在链表中的一个位置插入节点,需要先断开插入位置前后两个节点的链接,再和这两个节点建立新的链接:先cur->next = pre->next;再pre->next = cur; 如果先pre->next = cur,就会丢失sur这个节点的地址。

上面是普通的情况,那么针对头节点,尾节点的特殊情况呢? 末尾节点其实也没有什么特殊的,只是suc是NULL,也就是pre->next为NULL,按照上述的两步操作也是ok的。问题是在头节点前插入:
- 如果是普通链表,头节点前什么都没有,pre是NULL的。只进行①:cur->next = head,head = cur【头节点是新插入的节点】;
- 如果前面有虚拟头节点,那么pre就是dummy_head,头节点和其他节点是一样的操作;
删除节点
从链表中删除节点,可以是删除指定位置的,比如删除第三个节点;也可以是根据节点值删除的,比如删除值等于target的节点。删除节点时,将被删除节点的前面节点和后面节点连接起来的同时,断开被删除节点和其前面一个、后面一个节点的连接,并且要释放掉被删除节点的空间:pre->next = cur->next;delete cur。

针对头节点和尾节点的特殊情况呢? 将尾节点看作是下一个节点是null的节点,处理和其他节点一样。问题同样是头节点,删除头节点的话:
- 普通链表,直接tmp = head->next,delete head,head = tmp;
- 添加了虚拟头节点的链表,删除头节点,其pre = dummy_head,cur = head;直接按照正常节点删除即可。
从删除头节点和在头节点前插入节点的分析就可以知道,添加了虚拟头节点dummy_head后,对头节点的操作不需要单独讨论,所有节点操作一致。
查找节点
比如按位置查找某个节点,返回其节点值;比如按值查找链表中是否存在值等于target的节点。因为链表是通过节点的指针域而将各个节点连接起来的,它不能像数组一样直接按下标查找【数组各元素在内存空间是连续存储的,通过下标就能够定位到内存空间】。要查找链表中某个节点,需要遍历链表,需要O(n)的时间复杂度。
707. 设计链表
题目链接:707.设计链表
题目内容:

理解题意:实际上就是实现一个链表类,可以单向也可以双向,其中要涉及到插入节点:在头部插入、在尾部插入、根据下标index在指定位置插入;删除节点;根据下标index获取节点值。
实现:单向链表:
以下代码实现的是有虚拟头节点的单向链表,并且在类中定义一个_size变量存储链表中节点数量,以便根据下标index删除、添加节点时,判断下标是否合理。 另外在节点末尾处添加节点,可认为是index = _size时,在index处插入节点:
class MyLinkedList {
private://定义类的私有变量int _size; //链表中节点数量,不包括虚拟头节点//定义链表节点结构体struct ListNode {int val; //数据域ListNode *next; //指针域//构造函数ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}};//链表附加头节点,整个链表的开始ListNode* _dummyhead;
public: //自定义类的构造函数MyLinkedList() {_dummyhead = new ListNode(0); //新建虚拟头节点_size = 0; //初始化链表中有效节点数量为0}//根据下标返回节点valueint get(int index) {//下标无效if(index >= _size)return -1;ListNode* currNode = _dummyhead; //从虚拟头节点开始访问//遍历到下标为index的节点for(int i = 0; i <= index; i++){currNode = currNode->next;} return currNode->val; //返回value}//在头部添加节点void addAtHead(int val) {ListNode * newNode = new ListNode(val); //先构造一个新节点newNode->next = _dummyhead->next; //直接在虚拟头节点后插入_dummyhead->next = newNode;_size++; //插入节点后,链表节点数量+1}//在尾部添加节点 //实际上就是在index为_size的地方添加节点void addAtTail(int val) {addAtIndex(_size,val);}//在指定下标index位置处插入节点void addAtIndex(int index, int val) {if(index > _size) return ; //下标不合理ListNode* newNode = new ListNode(val); //构造一个新节点ListNode* prevNode = _dummyhead;//找到需要插入的地方while(index) {prevNode = prevNode->next;index--;} //插入节点 newNode->next = prevNode->next;prevNode->next = newNode;_size++; //节点数量++}//删除指定位置的节点void deleteAtIndex(int index) {if(index >= _size) return; //下标不合理ListNode* prevNode = _dummyhead;//找到要删除的节点的前一个节点while(index){prevNode = prevNode->next;index--;}//tmp为要删除的节点ListNode* tmp = prevNode->next;//要删除节点前后两个节点建立新连接prevNode->next = tmp->next;//删除tmp节点,释放空间delete tmp;_size--; //链表中节点数量-1;}
};
实现:双向链表
双向链表就是每个节点有两个指针,一个指向前驱节点(preNode),一个指向后驱节点(succNode),插入、删除节点时,需要对两个指针的指向都处理:
- 插入一个节点时,preNode->next = newNode; newNode->next = succNode; succNode->prior = newNode; newNode->prior = preNode;
- 删除一个节点时:preNode->next = succNode;succNode->prior = preNode;
这里需要注意的是,succNode实际上是currNode->next,如果是删除最后一个节点或者在最后一个节点后追加一个节点,succNode=NULL,为了和其他节点统一操作,同样在末尾增加一个虚拟尾节点。
双向链表可以双向遍历,在index位置插入、删除、查询节点值时,可以先判断index是在链表前半段还是后半段,确定是从前往后遍历更快,还是从后往前遍历更快。 代码如下(C++):
class MyLinkedList {
private:int _size; //链表中有效节点数量,不包括虚拟头节点、虚拟尾节点struct ListNode { //定义链表节点结构体int val;ListNode *next, *prior; //双向链表需要有两个指针ListNode() : val(0), next(nullptr), prior(nullptr) {}ListNode(int x) : val(x), next(nullptr), prior(nullptr) {}ListNode(int x, ListNode *next, ListNode *prior) : val(x), next(next), prior(prior) {}};ListNode *_dummyhead, *_dummytail; //虚拟头节点和虚拟尾节点
public: MyLinkedList() {_dummyhead = new ListNode(0); //虚拟头节点_dummytail = new ListNode(0); //虚拟尾节点//建立虚拟头节点和虚拟尾节点之间的连接_dummyhead->next = _dummytail; _dummytail->prior = _dummyhead;_size = 0; //链表中有效节点数量}int get(int index) {//下标无效if(index >= _size)return -1;ListNode *currNode;//判断index在前半段if(index < _size/2){currNode = _dummyhead;//从前往后遍历更快for(int i = 0; i <= index; i++){currNode = currNode->next;} }else{ //index在后半段currNode = _dummytail;//从后往前遍历更快for(int i = _size-1; i >= index ;i--){currNode = currNode->prior;}}return currNode->val;}//在头部添加节点,即在虚拟头节点后插入void addAtHead(int val) {ListNode *newNode = new ListNode(val);//建立四条新的连接_dummyhead->next->prior = newNode;newNode->next = _dummyhead->next;_dummyhead->next = newNode;newNode->prior = _dummyhead;_size++;}//在尾部插入节点,等于在index = _size的位置插入节点void addAtTail(int val) {addAtIndex(_size,val);}//在指定index处插入void addAtIndex(int index, int val) {if(index > _size) return ;ListNode* newNode = new ListNode(val);ListNode *prevNode = _dummyhead, *succNode = _dummytail;if(index < _size/2){ //从前往后更快定位到index前的节点for(int i = 0; i < index; i++){prevNode = prevNode->next;}succNode = prevNode->next;}else{ //从后往前更快定位到index前的节点for(int i = _size - 1; i >= index; i--){succNode = succNode->prior;}prevNode = succNode->prior;}//插入一个节点后,新增四条连接succNode->prior = newNode; newNode->next = succNode;prevNode->next = newNode;newNode->prior = prevNode;_size++;}//删除index处的节点void deleteAtIndex(int index) {if(index >= _size) return;ListNode *prevNode = _dummyhead, *succNode = _dummytail;//根据index和_size的关系,决定从前往后遍历还是从后往前遍历if(index < _size/2){for(int i = 0; i < index; i++){prevNode = prevNode->next;}succNode = prevNode->next->next;}else{for(int i = _size - 1; i > index; i--){succNode = succNode->prior;}prevNode = succNode->prior->prior;}ListNode* tmp = prevNode->next;//preNode和succNode之间建立双向连接prevNode->next = succNode; succNode->prior = prevNode;delete tmp;_size--;}
};
相关文章:
【leetcode 力扣刷题】链表基础知识 基础操作
链表基础知识 基础操作 链表基础操作链表基础知识插入节点删除节点查找节点 707. 设计链表实现:单向链表:实现:双向链表 链表基础操作 链表基础知识 在数据结构的学习过程中,我们知道线性表【一种数据组织、在内存中存储的形式】…...
关于openfeign调用时content-type的问题
问题1描述: 今天在A服务使用openfeign调用B服务的时候,发现经常会偶发性报错。错误如下: 情况为偶发,很让人头疼。 两个接口如下: A服务接口: delayReasonApi.test(student);就是使用openfeign调用B服务的…...
OpenCV 玩转图像和视频
为什么学OpenCV? • OpenCV ⽀持对图像缩放、旋转、绘制⽂字图形等基础操作 • OpenCV 库包含了很多计算机视觉领域常⻅算法:⽬标检测、⽬标跟踪等 OpenCV 简介 • OpenCV (Open Source Computer Vision) 是计算机视觉和机器学习软件库 • Intel 1999…...
技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?
LigaAI 的评论编辑器、附件展示以及富文本编辑器都支持在 Vue2(Web)与 Vue3(VSCode、lDEA)中使用。这样不仅可以在不同 Vue 版本的工程中间共享代码,还能为后续升级 Vue3 减少一定阻碍。 那么,同时兼容 Vue…...
基于ArcGis提取道路中心线
基于ArcGis提取道路中心线 文章目录 基于ArcGis提取道路中心线前言一、生成缓冲区二、导出栅格数据三、导入栅格数据四、新建中心线要素五、生成中心线总结 前言 最近遇到一个问题,根据道路SHP数据生成模型的时候由于下载的道路数据杂项数据很多,所以导…...
xcode14.3更新一系列问题
1. Missing file libarclite_iphoneos.a (Xcode 14.3) 解决方法 Xcode升级到14.3后编译失败,完整错误日志: File not found: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneo…...
1U和2U的服务器怎么选择
企业建设网站的过程中,离不开租用服务器的环节,服务器在多种场景里面都可以发挥作用,服务器租用渠道有哪些?1U、2U选哪种服务器比较好?大家跟着壹基比小鑫一起来了解具体内容吧! 1U、2U选哪种服务器比较好&…...
【SA8295P 源码分析】05 - SA8295P QNX Host 上电开机过程 进一步梳理(结合代码)
【SA8295P 源码分析】05 - SA8295P QNX Host 上电开机过程 进一步梳理(结合代码) 一、APPS PBL(Application Primary Boot Loader):固化在CPU ROM中1.1 APPS PBL 加载 XBL Loader1.2 XBL Loader加载验证并运行SMSS进行自检,自检完成后触发Warm Reset1.3 WarmRest后,APPS…...
【数据结构与算法】迪杰斯特拉算法
迪杰斯特拉算法 介绍 迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。 算法过程 设置…...
python爬虫-网页数据提取
import requests #headers 网页右键->Network->最下面的User-Agent复制。 headers {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"} #你想要的网址 url &q…...
ZigBee的Many-to-One和Source Routing
1. Many-to-One Routing Many-to-One Routing,是一种简单的路由机制,使得整个网络中的路由设备拥有回到中心节点的路由。 在这种机制下,中心节点周期性发送Many-to-One route discovery广播(协议栈默认设置为60s,可以…...
七夕节 Chinese Valentine‘s Day 的由来
农历七月初七是七夕节。Qixi Festival falls on the seventh day of the seventh lunar month. 以前有一个牛郎,和他的哥哥和嫂子住在一起。他放的一头牛曾经是天庭的一个神仙,但他违反天庭的戒律,变成牛放到了人间。As the story goes,once …...
掌握JDK21全新结构化并发编程,轻松提升开发效率!
1 概要 通过引入结构化并发编程的API,简化并发编程。结构化并发将在不同线程中运行的相关任务组视为单个工作单元,从而简化错误处理和取消操作,提高可靠性,并增强可观察性。这是一个预览版的API。 2 历史 结构化并发是由JEP 42…...
【SA8295P 源码分析】00 - 系列文章链接汇总 - 持续更新中
【SA8295P 源码分析】00 - 系列文章链接汇总 - 持续更新中 一、分区、下载、GPIO等杂项相关二、开机启动流程代码分析二、OpenWFD 显示屏模块三、Touch Panel 触摸屏模块四、QUPv3 及 QNX Host透传配置五、Camera 摄像头模块(当前正在更新中...)六、网络…...
TCP拥塞控制详解 | 6. 主动队列管理
网络传输问题本质上是对网络资源的共享和复用问题,因此拥塞控制是网络工程领域的核心问题之一,并且随着互联网和数据中心流量的爆炸式增长,相关算法和机制出现了很多创新,本系列是免费电子书《TCP Congestion Control: A Systems …...
前端学习清单
顺序不分先后。 技术名称技术描述技术链接HTML5HTML5是下一代的HTML标准,是一种用于结构化内容的标记语言。MDN|HTMLCSS3CSS3是CSS技术的升级版本,它的最大好处就是可以让网页设计师更加方便的为网页添加各种各样的样式,而不用再局限于文字、…...
go atomic原子操作详细解读
文章目录 概要1、基本知识1.1 原子操作是什么1.2 CPU怎么实现原子操作的? 2、atomic包2.1、 Add函数2.2、CompareAndSwap函数2.3、Swap函数2.4、Load函数2.5、Store函数 3、atomic.Value值 概要 atomic包是golang通过对底层系统支持的原子操作进行封装,…...
Vue用JSEncrypt对长文本json加密以及发现解密失败
哈喽 大家好啊,最近发现进行加密后 超长文本后端解密失败,经过看其他博主修改 JSEncrypt原生代码如下: // 分段加密,支持中文JSEncrypt.prototype.encryptUnicodeLong function (string) {var k this.getKey();//根据key所能编…...
Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)
默认Excel/PowerPoint折线图是这个样子的: 左右两侧都留了大块空白,很难看 解决方案 点击横坐标,双击,然后按下图顺序点击 效果...
C++的类成员对齐
这是个小语法点,之前我们的对齐方式都是使用#pragma pack,这个方式实际是依赖编译器,且粒度粗(如果#pragma pack(1)之后没有#pragma pack(),那就作用整个进程了)。在C11之后引入关键字alignas,以此来实现对齐更加便利,…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
