算法学习--链表

引言:为什么进行链表的学习?

  • 考察能力独特:链表能很好地考察应聘者对指针操作、内存管理的理解和运用能力,还能检验代码的鲁棒性,比如处理链表的插入、删除操作时对边界条件的处理。
  • 数据结构基础:链表是很多复杂数据结构和算法的基础,如图算法中的邻接表存储结构就用到了链表,在操作系统的内存管理中也常利用链表来管理空闲内存块等。

1 方法论

核心技巧                       适用场景                             时间复杂度优化                  经典例题 ───────────────────────────────────────────────────────────

快慢指针       链表环检测、找中间节点等      O(n) 复杂度下高效处理    环形链表 Ⅱ (142)         
虚拟头节点    头部插入/删除等边界简化操作   不改变复杂度但简化逻辑  删除链表的倒数第 N 个节点 递归         链表反转、合并有序链表等     利用递归特性简化操作逻辑  合并两个有序链表 (21)      

*设计链表

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

单链表中的节点应该具备两个属性: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 的节点。
#define MAX(a, b) ((a) > (b) ? (a) : (b))typedef struct {struct ListNode *head;int size;
} MyLinkedList;struct ListNode *ListNodeCreat(int val) {struct ListNode * node = (struct ListNode *)malloc(sizeof(struct ListNode));node->val = val;node->next = NULL;return node;
}MyLinkedList* myLinkedListCreate() {MyLinkedList * obj = (MyLinkedList *)malloc(sizeof(MyLinkedList));obj->head = ListNodeCreat(0);obj->size = 0;return obj;
}int myLinkedListGet(MyLinkedList* obj, int index) {if (index < 0 || index >= obj->size) {return -1;}struct ListNode *cur = obj->head;for (int i = 0; i <= index; i++) {cur = cur->next;}return cur->val;
}void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {if (index > obj->size) {return;}index = MAX(0, index);obj->size++;struct ListNode *pred = obj->head;for (int i = 0; i < index; i++) {pred = pred->next;}struct ListNode *toAdd = ListNodeCreat(val);toAdd->next = pred->next;pred->next = toAdd;
}void myLinkedListAddAtHead(MyLinkedList* obj, int val) {myLinkedListAddAtIndex(obj, 0, val);
}void myLinkedListAddAtTail(MyLinkedList* obj, int val) {myLinkedListAddAtIndex(obj, obj->size, val);
}void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {if (index < 0 || index >= obj->size) {return;}obj->size--;struct ListNode *pred = obj->head;for (int i = 0; i < index; i++) {pred = pred->next;}struct ListNode *p = pred->next;pred->next = pred->next->next;free(p);
}void myLinkedListFree(MyLinkedList* obj) {struct ListNode *cur = NULL, *tmp = NULL;for (cur = obj->head; cur;) {tmp = cur;cur = cur->next;free(tmp);}free(obj);
}

2 快慢指针

 2.1 介绍

溯找前驱;链表超长且环大时,快指针环内多圈追赶,有性能损耗。快慢指针是链表操作的高效技巧,通过设置快、慢两个指针,快指针每次移动两步,慢指针每次移动一步,利用二者速度差达成特定目标。

  • 适用场景
    • 链表环检测:精准判断链表有无环,有环时能定位环入口,如在复杂数据结构中排查循环引用隐患。
    • 找中间节点:像归并排序前期需快速平分链表,快慢指针遍历一次即可定位中点,为后续有序处理奠基。
    • 判断奇偶节点:依快慢指针最终位置,轻松判别节点奇偶性,辅助特殊逻辑,如奇偶位交替变换。
  • 时间复杂度优化原理:利用速度差,无环时快指针率先触尾结束遍历;有环时快指针必在环内 “追上” 慢指针,单次 O (n) 遍历就解决问题,找中点同理,快到尾时慢恰在中点。
  • 选择策略:但凡涉及链表节点位置判断,像找特定节点、查环,尤其单次遍历需多元位置信息,优先启用。
  • 局限性:单向链表中,快慢指针难以直接回

2.2 练习

(1)环形链表Ⅱ

环形链表 IIhttps://leetcode.cn/problems/c32eOV/

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *detectCycle(struct ListNode *head) {struct ListNode* fast = head;struct ListNode* slow = head;while(fast != NULL && fast->next != NULL) {slow = slow->next;fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直⾄相遇
if (slow == fast) {struct ListNode* index1 = fast;struct ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;}
return index2; // 返回环的⼊⼝}}
return NULL;
}

  (2)相交链表

 相交链表https://leetcode.cn/problems/intersection-of-two-linked-lists/

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode * pA = headA;struct ListNode * pB = headB;int lengthA = 0;int lengthB = 0;while(headA != NULL){lengthA ++;headA = headA->next;}while(headB != NULL){lengthB ++;headB = headB->next;}if(lengthA < lengthB){for(int i = 0;i < abs(lengthA - lengthB);i++){pB = pB->next;}}else{for(int i = 0;i < abs(lengthA - lengthB);i++){pA = pA->next;}}while(pA != NULL && pB != NULL){if(pA == pB){return pA;}pA = pA->next;pB = pB->next;}return NULL;}

(3)删除链表的倒数第N个结点 

删除链表的倒数第 N 个结点https://leetcode.cn/problems/SLwz0R/

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/struct ListNode* removeNthFromEnd(struct ListNode* head, int n){// 创建虚拟头节点,方便处理删除头节点的情况struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));dummy->val = 0;dummy->next = head;struct ListNode* first = dummy;struct ListNode* second = dummy;// 让 first 指针先移动 n + 1 步for (int i = 0; i <= n; i++) {first = first->next;}// 同时移动 first 和 second 指针,直到 first 指针到达链表末尾while (first != NULL) {first = first->next;second = second->next;}// 此时 second 指针指向要删除节点的前一个节点struct ListNode* temp = second->next;second->next = second->next->next;free(temp);// 获取新的头节点struct ListNode* newHead = dummy->next;free(dummy);return newHead;
}

3 虚拟头节点

    3.1 介绍    

虚拟头节点是链表操作的辅助 “锚点”,不存实质数据,占位简化逻辑。

  • 适用场景
    • 头部插入删除:常规操作头节点需额外边界判断,虚拟头使插入、删除逻辑统一,降低出错概率。
    • 链表初始化:为空链表开篇或构建新结构 “打头阵”,后续添加节点顺理成章。
    • 多链表操作:合并多链表时,提供统一起始,梳理合并流程,代码结构更清晰。
  • 时间复杂度优化原理:统一头部操作,规避重复判断,多次操作下稳定耗时,提升整体效率。
  • 选择策略:频繁头部操作或统一管理多链表,又或需明确起始且简化头节点处理时,它是首选。
  • 局限性:占用额外空间存储虚拟节点;少量头部操作时,引入虚拟头会使代码繁杂,得不偿失。

   3.2 练习

(1)移除链表元素

移除链表元素https://leetcode.cn/problems/remove-linked-list-elements/

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

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode dummy;dummy.next = head;struct ListNode* current = &dummy;while (current->next) {if (current->next->val == val) {struct ListNode* temp = current->next;current->next = current->next->next;free(temp);} else {current = current->next;}}return dummy.next;
}
struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* phead = NULL;struct ListNode* ptail = NULL;struct ListNode* pcur = head;while(pcur){if(pcur->val != val){if(phead == NULL){phead = ptail = pcur;}else{ptail->next = pcur;ptail = pcur;}}pcur = pcur->next;}if(ptail)ptail->next = NULL;return phead;
}

4 递归 

 4.1 介绍

 递归是基于自身定义问题求解的策略,将链表大问题拆解为同构子问题处理。

  • 适用场景
    • 链表反转:从尾到头逐节点反转,递归贴合天然顺序,代码简洁直观。
    • 合并有序链表:对比、拼接子链表,递归处理层次分明,轻松融合多链表。
    • 链表遍历操作:深度优先遍历修改节点值等,递归按链表结构递进,无需复杂循环。
  • 时间复杂度优化原理:分解难题,各子问题独立递归求解,省却多层嵌套循环,顺链表结构操作,削减冗余步骤。
  • 选择策略:操作有递归特性,子问题相似,如反转、合并场景;追求代码精简、逻辑通透且时间要求适度时优先考虑。
  • 局限性:长链表易引发栈溢出,因递归需栈存调用状态;空间占用多,函数调用开销也会拖慢性能。

4.2 练习 

(1)反转链表

反转链表https://leetcode.cn/problems/UHnkqh/

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

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/struct ListNode* reverseList(struct ListNode* head){struct ListNode *prev = NULL;struct ListNode *current = head;struct ListNode *nextNode;while (current != NULL) {// 保存当前节点的下一个节点nextNode = current->next;// 将当前节点的 next 指针指向前一个节点current->next = prev;// 更新前一个节点为当前节点prev = current;// 更新当前节点为之前保存的下一个节点current = nextNode;}// 最后 prev 指向反转后链表的头节点return prev;
}

(2)合并两个有序链表

合并两个有序链表https://leetcode.cn/problems/merge-two-sorted-lists/

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {// 创建虚拟头节点struct ListNode dummy = {0, NULL};struct ListNode* tail = &dummy;// 遍历两个链表,比较节点值并合并while (list1 && list2) {if (list1->val < list2->val) {tail->next = list1;list1 = list1->next;} else {tail->next = list2;list2 = list2->next;}tail = tail->next;}// 将剩余的节点连接到新链表尾部if (list1) {tail->next = list1;}if (list2) {tail->next = list2;}return dummy.next;
}

学习时间 2025.02.11 

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

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

相关文章

域名劫持原理与实践

了解域名及域名劫持 由于点分十进制的IP地址难于记忆&#xff0c;便出现了域名。由于网络传输中最终还是基于IP&#xff0c;所以必须通过一种机制将IP和域名一一对应起来&#xff0c;这便是DNS。全球总共有13台根域名服务器。 域名劫持是互联网攻击中常见的一种攻击方式&…

【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第二部分:(训练硬件)基础设施

论文原文链接&#xff1a;DeepSeek-V3/DeepSeek_V3.pdf at main deepseek-ai/DeepSeek-V3 GitHub 特别声明&#xff0c;本文不做任何商业用途&#xff0c;仅作为个人学习相关论文的翻译记录。本文对原文内容直译&#xff0c;一切以论文原文内容为准&#xff0c;对原文作者表示…

MapReduce到底是个啥?

在聊 MapReduce 之前不妨先看个例子&#xff1a;假设某短视频平台日活用户大约在7000万左右&#xff0c;若平均每一个用户产生3条行为日志&#xff1a;点赞、转发、收藏&#xff1b;这样就是两亿条行为日志&#xff0c;再假设每条日志大小为100个字节&#xff0c;那么一天就会产…

Error: llama runner process has terminated: exit status 0xc0000409 问题解决办法

在大模型部署过程中&#xff0c;格式转换环节若使用了高版本的 llama.cpp 库&#xff0c;而系统当前运行的版本较低&#xff0c;就会出现版本不兼容的情况。 这种不匹配会阻碍模型的正常运行&#xff0c;进而导致报错。建议你密切关注模型所需的版本要求&#xff0c;及时将系统…

代码随想录-训练营-day20

今天我们继续回溯&#xff1a; 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 这个题和我们之前的组合题相比&#xff0c;最大的区别在于我们可以无限次的重复取用某值了&#xff0c;这就让我们的递归参数与之前不同&#xff0c;除此之外&#xff0c;本质上这个题与21…

ubuntu 本地部署deepseek r1 蒸馏模型

本文中的文件路径或网络代理需要根据自身环境自行删改 一、交互式chat页面 1.1 open-webui 交互窗口部署&#xff1a;基于docker安装&#xff0c;且支持联网搜索 Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台&#xff0c;旨在完全离线操作。它支持各种 LLM…

数据库 绪论

目录 数据库基本概念 一.基本概念 1.信息 2.数据 3.数据库&#xff08;DB&#xff09; 4.数据库管理系统&#xff08;DBMS&#xff09; 5.数据库系统&#xff08;DBS&#xff09; 二.数据管理技术的发展 1.人工管理阶段 2.文件系统阶段 3.数据库系统阶段 4.数据库管…

数据中台是什么?:架构演进、业务整合、方向演进

文章目录 1. 引言2. 数据中台的概念与沿革2.1 概念定义2.2 历史沿革 3. 数据中台的架构组成与关键技术要素解析3.1 架构组成3.2 关键技术要素 4. 数据中台与其他平台的对比详细解析 5. 综合案例&#xff1a;金融行业数据中台落地实践5.1 背景5.2 解决方案5.3 成果与价值 6. 方向…

【DeepSeek】DeepSeek概述 | 本地部署deepseek

目录 1 -> 概述 1.1 -> 技术特点 1.2 -> 模型发布 1.3 -> 应用领域 1.4 -> 优势与影响 2 -> 本地部署 2.1 -> 安装ollama 2.2 -> 部署deepseek-r1模型 1 -> 概述 DeepSeek是由中国的深度求索公司开发的一系列人工智能模型&#xff0c;以其…

如何使用C++将处理后的信号保存为PNG和TIFF格式

在信号处理领域&#xff0c;我们常常需要将处理结果以图像的形式保存下来&#xff0c;方便后续分析和展示。C提供了多种库来处理图像数据&#xff0c;本文将介绍如何使用stb_image_write库保存为PNG格式图像以及使用OpenCV库保存为TIFF格式图像。 1. PNG格式保存 使用stb_ima…

查出 product 表中所有 detail 字段包含 xxx 的完整记录

您可以使用以下 SQL 查询语句来查出 product 表中所有 detail 字段包含 oss.kxlist.com 的完整记录&#xff1a; SELECT * FROM product WHERE INSTR(detail, oss.kxlist.com) > 0;下面是detail字段包含的完整内容 <p><img style"max-width:100%;" src…

微服务 day01 注册与发现 Nacos OpenFeign

目录 1.认识微服务&#xff1a; 单体架构&#xff1a; 微服务架构&#xff1a; 2.服务注册和发现 1.注册中心&#xff1a; 2.服务注册&#xff1a; 3.服务发现&#xff1a; 发现并调用服务&#xff1a; 方法1&#xff1a; 方法2&#xff1a; 方法3:OpenFeign OpenFeig…

Shell原理简介与Linux中的权限问题

一、Shell命令及运行原理 1.1通常说的计算机体系结构指的是什么 通常意义上的计算机体系结构指的是芯片&#xff1a; 如锐龙amd&#xff0c;英特尔酷睿intel core 他们分为 x86&#xff1a;32位 x86_64&#xff1a;64位 两种 1.2广义上的Linux系统分为哪些部分&#xf…

在rtthread中,scons构建时,它是怎么知道是从rtconfig.h找宏定义,而不是从其他头文件找?

在rtthread源码中&#xff0c;每一个bsp芯片板级目录下都有一个 SConstruct scons构建脚本的入口&#xff0c; 在这里把rtthread tools/目录下的所有模块都添加到了系统路径中&#xff1a; 在tools下所有模块中&#xff0c;最重要的是building.py模块&#xff0c;在此脚本里面…

C# Winform 使用委托实现C++中回调函数的功能

C# Winform 使用委托实现C中回调函数的功能 在项目中遇到了使用C#调用C封装的接口&#xff0c;其中C接口有一个回调函数的参数。参考对比后&#xff0c;在C#中是使用委托(delegate)来实现类似的功能。 下面使用一个示例来介绍具体的使用方式&#xff1a; 第一步&#xff1a;…

深度学习之神经网络框架搭建及模型优化

神经网络框架搭建及模型优化 目录 神经网络框架搭建及模型优化1 数据及配置1.1 配置1.2 数据1.3 函数导入1.4 数据函数1.5 数据打包 2 神经网络框架搭建2.1 框架确认2.2 函数搭建2.3 框架上传 3 模型优化3.1 函数理解3.2 训练模型和测试模型代码 4 最终代码测试4.1 SGD优化算法…

2025.2.9 每日学习记录2:技术报告写了一半+一点点读后感

0.近期主任务线 1.完成小论文准备 目标是3月份完成实验点1的全部实验和论文。 2.准备教资笔试 打算留个十多天左右&#xff0c;一次性备考笔试的三个科目 1.实习申请技术准备&#xff1a;微调、Agent、RAG 1.今日完成任务 1.电子斗蛐蛐&#xff08;文本书写领域&am…

9 Pydantic复杂数据结构的处理

在构建现代 Web 应用时&#xff0c;我们往往需要处理复杂的输入和输出数据结构。例如&#xff0c;响应数据可能包含嵌套字典、列表、元组&#xff0c;甚至是多个嵌套对象。Pydantic 是一个强大的数据验证和序列化库&#xff0c;可以帮助我们轻松地处理这些复杂的数据结构&#…

链表(LinkedList) 1

上期内容我们讲述了顺序表&#xff0c;知道了顺序表的底层是一段连续的空间进行存储(数组)&#xff0c;在插入元素或者删除元素需要将顺序表中的元素整体移动&#xff0c;时间复杂度是O(n)&#xff0c;效率比较低。因此&#xff0c;在Java的集合结构中又引入了链表来解决这一问…

torch_bmm验算及代码测试

文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘&#xff0c;比如&#xff0c; mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…