LeetCode算法题 (设计链表)Day16!!!C/C++

https://leetcode.cn/problems/design-linked-list/description/

一、题目分析

        

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

        今天的这道题目算得上是目前碰到的最多字数的题了,但是需求很简单,实现出四个功能即可。分别为:

  1. int get(int index)获取链表中下标为index的节点的值
  2. void addAtHead(int val)将一个val的节点插入到链表中第一个元素之前,也就是头插操作。
  3. void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素,也就是尾插操作。
  4. void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前,这里对应随机插入操作。
  5. void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

二、示例分析

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

三、设计思路&代码实现

        首先题目说明可以使用单链表||双链表,这里我们选用单链表的解法,只要掌握了单链表的一些基本操作,再使用双链表进行实现起来就会很容易。

 一、创建结构体

        这里我们需要创建一个结构体,之前在和大家分享链表的中间节点那里,有提到过链表这种数据结构的存储方式,如果忘记的同学,可以点击链接重新回忆一下。

https://blog.csdn.net/m0_75144071/article/details/144828160?spm=1001.2014.3001.5502

// 定义链表节点的结构体
struct LinkNode {int val;          // 节点存储的数据值LinkNode* next;   // 指向下一个节点的指针// 构造函数,用于初始化新节点// 参数 val: 要存储在节点中的值LinkNode(int val) : val(val),     // 初始化 val 成员为传入的值next(nullptr)  // 初始化 next 指针为 nullptr(表示这是尾节点){// 构造函数体为空,因为初始化列表已经完成了所有初始化工作}
};

         LinkNode是一个表示节点的结构体,包含两个成员,分别为val用于存储节点的数据(本题用到的数据类型为int型),next用于指向一下个节点的指针(LinkNode*类型)

二、初始化链表

        在昨天的题目中给大家介绍了使用虚拟头节点进行一些链表的基本操作时会很方便,今天我们同样采用虚拟带有虚拟头节点的方式来实现。忘记的同学,可以点击链接再重新回顾一下。

https://blog.csdn.net/m0_75144071/article/details/147662796?spm=1001.2014.3001.5502

// MyLinkedList 类的构造函数
MyLinkedList() {// 创建一个虚拟头节点(dummy node),其值初始化为0// 虚拟头节点不存储实际数据,它的存在是为了简化链表操作// 例如在链表头部插入/删除节点时不需要特殊处理dummyHead = new LinkNode(0);// 初始化链表大小为0,表示当前链表为空(只有虚拟头节点)size = 0;
}

三、查找功能(按位序查找)

        链表这种数据结构有一种缺点,不支持随机访问,也就是他没有办法做到像数组那样,我只要有了元素的下标就可以用常数级的时间复杂度来找到这个元素。使用链表查找元素时复杂度为O(n)(最坏)。所以我们需要从头开始遍历才可以进行查找。

/*** 获取链表中指定位置的元素值* index 要获取的元素位置索引(从0开始)* return 如果索引有效则返回对应节点的值,否则返回-1*/
int get(int index) {// 边界检查:如果索引超出有效范围(小于0或大于等于链表长度)// 则返回-1表示无效if (index > (size - 1) || index < 0)return -1;// 初始化当前指针指向第一个实际节点(跳过虚拟头节点)LinkNode* cur = dummyHead->next;// 遍历链表直到目标位置// 使用index--的方式移动指针,循环次数即为需要移动的步数while (index--) {cur = cur->next;}// 返回找到的节点的值return cur->val;
}

四、头插操作

        在进行头插入/头删除操作时候,链表的效率就会比顺序表(数组)高很多, 链表的复杂度为O(1),而数组则需要O(n)。

        

/*** 在链表头部插入一个新节点* val 要插入的节点值*/
void addAtHead(int val) {LinkNode* newNode = new LinkNode(val);// 新节点指向当前第一个节点newNode->next = dummyHead->next;// 更新dummyHead指向新节点dummyHead->next = newNode;// 增加链表长度size++;
}

以下为图文讲解: 

五、尾插操作

        链表的尾插操作由于每次需要从头节点开始遍历找到最后的尾部节点,所以整体时间复杂度为O(n)。

void addAtTail(int val) {LinkNode* newNode = new LinkNode(val); newNode->next = nullptr;  // 由于是单链表的尾部插入,所以next的的指向应为nullptrLinkNode* cur = dummyHead;while (cur->next) {  // 遍历直到找到最后一个节点cur = cur->next;}cur->next = newNode;  // 将新节点接在尾部size++; // 增加链表大小
}

图文讲解: 

 

六、按位序的插入操作

        在进行按位序插入操作时,链表整体的时间复杂度以查找位置为主导为O(n) (最坏情况下),相比数组来说,时间复杂度整体相同。

/*** 在链表的指定位置插入一个新节点* index 要插入的位置索引(从0开始)* val 要插入的节点值*/
void addAtIndex(int index, int val) {// 1. 边界检查:如果索引大于当前链表长度或索引小于0,直接返回不执行插入if (index > size || index < 0)return;// 2. 创建新节点,使用构造函数直接初始化节点值LinkNode* newNode = new LinkNode(val);// 3. 定位到要插入位置的前驱节点LinkNode* cur = dummyHead;  // 从虚拟头节点开始while (index--) {           // 循环index次,移动到插入位置的前一个节点cur = cur->next;}// 4. 执行插入操作newNode->next = cur->next;  // 新节点的next指向原位置的节点cur->next = newNode;        // 前驱节点的next指向新节点// 5. 更新链表长度size++;
}

图文解释:

七、按位序的删除操作 

        在链表中的按位序删除与插入相同。链表整体的时间复杂度以查找位置为主导为O(n) (最坏情况下),相比数组来说,时间复杂度整体相同。

// 删除指定索引位置的节点
// 参数:
// index: 要删除节点的索引,索引从 0 开始
void deleteAtIndex(int index) {// 检查索引是否越界,如果索引大于等于链表的大小或者小于 0,直接返回if (index >= size || index < 0)return;// 定义一个指针 cur,初始指向虚拟头节点 dummyHead// 虚拟头节点的作用是简化链表头节点的操作LinkNode* cur = dummyHead;// 通过循环让 cur 指针移动到要删除节点的前一个节点// 循环条件 index-- 表示每次循环后 index 的值减 1,直到 index 变为 0while (index--) {cur = cur->next;}// 定义一个临时指针 temp,指向要删除的节点// 即 cur 指针当前所指节点的下一个节点LinkNode* temp = cur->next;// 调整 cur 指针的 next 指针,使其跳过要删除的节点// 直接指向要删除节点的下一个节点cur->next = cur->next->next;// 释放要删除节点所占用的内存,防止内存泄漏delete temp;// 链表的大小减 1,因为成功删除了一个节点size--;
}

图文讲解:

至此本题需实现的所有功能均已实现完毕!完结撒花!!!🌸🌸🌸

八、完整代码

        C++:

class MyLinkedList {
public:struct LinkNode {int val;LinkNode* next;LinkNode(int val) : val(val), next(nullptr) {}};MyLinkedList() {dummyHead = new LinkNode(0);size = 0;}int get(int index) {if (index > (size - 1) || index < 0)return -1;LinkNode* cur = dummyHead->next;while (index--) {cur = cur->next;}return cur->val;}void addAtHead(int val) {LinkNode* newNode = new LinkNode(0);newNode->val = val;newNode->next = dummyHead->next;dummyHead->next = newNode;size++;}void addAtTail(int val) {LinkNode* newNode = new LinkNode(0);newNode->val = val;newNode->next = NULL;LinkNode* cur = dummyHead;while (cur->next) {cur = cur->next;}cur->next = newNode;size++;}void addAtIndex(int index, int val) {if (index > size)return;LinkNode* newNode = new LinkNode(val);LinkNode* cur = dummyHead;while (index--) {cur = cur->next;}newNode->next = cur->next;cur->next = newNode;size++;}void deleteAtIndex(int index) {if (index >= size || index < 0)return;LinkNode* cur = dummyHead;while (index--) {cur = cur->next;}LinkNode* temp = cur->next;cur->next = cur->next->next;delete temp;size--;}void Print() {LinkNode* cur = dummyHead->next;while (cur->next) {cout << cur->val << " ";cur = cur->next;}cout << endl;}private:int size;LinkNode* dummyHead;
};/*** Your MyLinkedList object will be instantiated and called as such:* MyLinkedList* obj = new MyLinkedList();* int param_1 = obj->get(index);* obj->addAtHead(val);* obj->addAtTail(val);* obj->addAtIndex(index,val);* obj->deleteAtIndex(index);*/

四、链表与数组的对比

链表 vs 数组 操作对比总结

时间复杂度对比
操作链表数组胜出方
随机访问O(n)O(1)数组
头部插入/删除O(1)O(n)链表
尾部插入/删除O(n)O(1)数组
中间插入/删除O(n)O(n)平手
核心特点
  • 链表:动态内存、插入删除快(尤其头部)、访问慢

  • 数组:连续内存、访问快、插入删除慢(需移动元素)

适用场景
  • 链表:频繁在头部/中部增删(如栈、队列)

  • 数组:频繁随机访问或尾部操作(如数值计算)

五、题目总结 

        这道题目让我们自己动手实现一个链表数据结构,主要包含获取节点值、头部插入、尾部插入、指定位置插入和删除这五个核心操作。通过这个实现过程,我们深入理解了链表的基本特性和操作原理。

        链表最大的特点是插入删除高效,尤其是头部操作只需要O(1)时间,这比数组要快很多。但是链表访问元素需要从头遍历,时间复杂度是O(n),不如数组的随机访问快。在实际开发中,如果经常需要在头部插入删除数据,链表是更好的选择;如果需要频繁按位置访问数据,数组会更合适。

        实现链表时要注意几个关键点:使用虚拟头节点可以简化操作;维护链表长度size变量能方便边界检查;指针操作要特别注意顺序,避免出现断链的情况。这些细节处理能力是成为一名合格程序员的基本功。

        通过这道题目,我们不仅掌握了链表的实现方法,更重要的是理解了不同数据结构的适用场景。在实际工程中,要根据具体需求选择最合适的数据结构,这是写出高效代码的重要基础。链表作为基础数据结构,它的思想在很多高级数据结构中都有体现,学好链表对后续学习树、图等结构很有帮助。今天的分享到此结束!谢谢大家!!!荆轲刺秦!!!

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

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

相关文章

《解锁GCC版本升级:开启编程新世界大门》

《解锁GCC版本升级:开启编程新世界大门》 一、引言:GCC 版本升级的魔法钥匙 在编程的广阔天地里,GCC(GNU Compiler Collection)宛如一座灯塔,为无数开发者照亮前行的道路。它是一款开源且功能强大的编译器集合,支持 C、C++、Objective - C、Fortran、Ada 等多种编程语言…

toLua笔记

基本 LuaState luaStatenew LuaState(); luaState.Start(); luaState.DoString("xxx"); luaState.DoFile("yyy.lua"); luaState.Require("zzz");//不要加.lua后缀 luaState.CheckTop();//检查解析器栈顶为空 luaState.Dispose(); luaStatenull;…

go实现双向链表

需求 实现双向链表的节点生成、正反向遍历、指定删除。 实现 package mainimport ("fmt" )type zodiac_sign struct {number intdizhi stringanimal stringyear intprevious *zodiac_signnext *zodiac_sign }// 添加 // func add_node_by_order(pr…

AI实践指南:AGENT、RAG和MCP在Java中的简单实现

在当今AI快速发展的时代&#xff0c;有几个核心概念正在改变我们构建智能应用的方式。本文将用简单易懂的语言介绍三个重要概念&#xff1a;AGENT&#xff08;AI代理&#xff09;、RAG&#xff08;检索增强生成&#xff09;和MCP&#xff08;多通道感知&#xff09;&#xff0c…

解决VMware虚拟机能搜索到网页但打不开的问题

&#x1f334; 问题描述 很奇怪&#xff0c;不知道为什么&#xff0c;我安装的Windows 10虚拟机能在浏览器中搜索到网页&#xff0c;但点击具体的网页链接就是死活不能加载出来&#xff0c;如下图所示&#xff1a; 点击第一个链接&#xff0c;加载了四五分钟&#xff0c;结果就…

JVM性能调优的基础知识 | JVM内部优化与运行时优化

目录 JVM内部的优化逻辑 JVM的执行引擎 解释执行器 即时编译器 JVM采用哪种方式&#xff1f; 即时编译器类型 JVM的分层编译5大级别&#xff1a; 分层编译级别&#xff1a; 热点代码&#xff1a; 如何找到热点代码&#xff1f; java两大计数器&#xff1a; OSR 编译…

什么是多租户系统

随着云计算和 SaaS&#xff08;Software as a Service&#xff09;模式的普及&#xff0c;多租户架构&#xff08;Multi-Tenant Architecture&#xff09;成为 SaaS 产品设计中的核心模式之一。多租户架构允许多个用户&#xff08;租户&#xff09;共享同一套基础设施和应用&am…

多线程系列三:这就是线程的状态?

1.认识线程的状态 NEW&#xff1a;Thread对象已经创建好了&#xff0c;但还没有调用start方法在系统中创建线程 RUNNABLE&#xff1a;就绪状态&#xff0c;表示这个线程正在CPU上执行&#xff0c;或准备就绪&#xff0c;随时可以去CPU上执行 BLOCKED&#xff1a;表示由于锁竞争…

【C语言练习】019. 使用结构体数组存储复杂数据

019. 使用结构体数组存储复杂数据 019. 使用结构体数组存储复杂数据示例1&#xff1a;定义一个结构体并创建结构体数组定义结构体创建并初始化结构体数组输出结果 示例2&#xff1a;动态输入数据到结构体数组定义结构体动态输入数据示例输入和输出 示例3&#xff1a;使用结构体…

**Java面试大冒险:谢飞机的幽默与技术碰撞记**

互联网大厂Java求职者面试&#xff1a;一场严肃与搞笑交织的技术盛宴 场景&#xff1a; 互联网大厂面试间 人物&#xff1a; 面试官&#xff1a; 一位严肃的资深架构师&#xff0c;对技术要求严格。谢飞机&#xff1a; 一位搞笑的程序员&#xff0c;技术实力参差不齐。 第一…

MySQL进阶(三)

五、锁 1. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff08;避免争抢&#xff09;。 在数据库中&#xff0c;除传统的计算资源&#xff08;如 CPU、RAM、I/O 等&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发…

【BLE】【nRF Connect】 精讲nRF Connect自动化测试套件(宏录制、XML脚本)

目录 前言 1. nRF Connect自动化测试介绍 1.1. nRF connect宏录制功能介绍 1.2. 电脑端XML方式 1.3 实际应用案例 1.3.1 BLE 稳定性测试 1.3.2 设备固件更新(DFU)测试 1.3.3 批量设备配置 1.4 操作步骤 1.5 注意事项 2. nRF Connect日志记录 2.1. 日志记录功能 …

【数据结构】堆的完整实现

堆的完整实现 堆的完整实现GitHub地址前言堆的核心功能实现重温堆的定义堆结构定义1. 堆初始化与销毁2. 元素交换函数3. 堆化操作向上调整&#xff08;子→父&#xff09;向下调整&#xff08;父→子&#xff09; 4. 堆元素插入5. 堆元素删除6. 辅助功能函数堆的判空获取堆顶元…

如何优化MySQL主从复制的性能?

优化MySQL主从复制的性能需要从硬件、配置、架构设计和运维策略等多方面入手。以下是详细的优化方案&#xff1a; 一、减少主库写入压力 1. ‌主库优化‌ 二进制日志&#xff08;binlog&#xff09;优化‌&#xff1a; 使用 binlog_formatROW 以获得更高效的复制和更少的数…

MySQL安装完全指南:从零开始到配置优化(附避坑指南)

&#x1f525; 前言&#xff1a;为什么你总是装不好MySQL&#xff1f; &#xff08;实话实说&#xff09;每次看到新手在MySQL安装环节疯狂踩坑&#xff0c;老司机都忍不住想摔键盘&#xff01;明明官网下载的安装包&#xff0c;怎么就会报错呢&#xff1f;为什么别人的环境变…

密码学_加密

目录 密码学 01 密码基础进制与计量 02 加解密基操 替换 移位 编码 编码 置换 移位 加解密强度 03 对称加密算法(私钥) 工作过程 缺陷 对称加密算法列举&#xff1f; DES DES算法架构 DES分组加密公式 DES中ECB-CBC两种加密方式 3DES 由于DES密钥太短&#xf…

轻量级RTSP服务模块:跨平台低延迟嵌入即用的流媒体引擎

在音视频流媒体系统中&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09;服务模块通常扮演着“视频分发中心”的角色&#xff0c;它将编码后的音视频内容转为标准的流媒体格式&#xff0c;供客户端&#xff08;播放器、云端平台、AI模块等&#xff09;拉流…

Nginx发布Vue(ElementPlus),与.NETCore对接(腾讯云)

案例资料链接&#xff1a;https://download.csdn.net/download/ly1h1/90745660 1.逻辑说明 1.1 逻辑示意图 # 前端请求处理逻辑图浏览器请求流程: 1. 浏览器发起请求├─ 开发环境(DEV)│ ├─ 请求URL: http://192.168.0.102:3000/api/xxx│ └─ 被Vite代理处理└─ 生产…

解析机器人 2.0.2 | 支持超过50种短视频平台的链接解析,无水印提取,多功能下载工具

解析机器人是一款功能强大的工具软件&#xff0c;登录即可解锁会员特权。它支持超过50种短视频平台的链接解析&#xff0c;包括抖音、快手、西瓜、bilibili等&#xff0c;并能实现无水印提取。此外&#xff0c;还提供P2P下载、磁力链等多种下载方式&#xff0c;确保用户能够快速…

C++ - 数据容器之 forward_list(创建与初始化、元素访问、容量判断、元素遍历、添加元素、删除元素)

一、创建与初始化 引入 <forward_list> 并使用 std 命名空间 #include <forward_list>using namespace std;创建一个空 forward_list forward_list<int> fl;创建一个包含 5 个元素&#xff0c;每个元素初始化为 0 的 forward_list forward_list<int&g…