【基础算法总结】链表篇

目录

  • 一, 链表常用技巧和操作总结
  • 二,算法原理和代码实现
    • 2.两数相加
    • 24.两两交换链表中的节点
    • 143.重排链表
    • 23.合并k个升序链表
    • 25.k个一组翻转链表
  • 三,算法总结

一, 链表常用技巧和操作总结

有关链表的算法题也是一类常见并且经典的题型,这些题要使用数据结构中我们手搓的链表结构,也要使用STL中的 list 容器

下面是几点常用操作的总结
(1) 善于画图
(2) 引入虚拟头结点
方便处理边界情况,就是当没节点第一次头插或尾插的时候的那个判断,引入虚拟头结点就可以避免这个判断(写成 ptail = newHead)
(3) 不要吝啬空间,大胆定义变量。
(4) 快慢双指针的使用
主要应用是判环,找链表中环的入口,找链表中倒数第 n 个节点
(5) 解决链表类的题,一般是:创建一个新节点,尾插操作,头插操作

我们在学习数据结构时也做过许多有关链表类的经典题目,请浏览:【C/数据结构与算法】10道链表经典OJ。
里面的一些题目与本篇文章的题目也是有关联的,这篇文章算是进阶篇

二,算法原理和代码实现

2.两数相加

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

这道题是一道简单的模拟题,模拟两数相加即可
定义两个指针 cur1和cur2用于遍历两个链表,再定义一个整数 t 用于进位。用 t 和每个节点的值相加,取出 t 的个位创建新节点
在这里插入图片描述

细节问题:

(1) 这里要注意的细节就是链表是逆序的,其实这也是方便我们操作,因为我们可以直接从个位开始相加
(2) 1. 循环条件:cur1 || cur2 || t
这里要或t是原因:当两个指针都走完时,t里可能还有进位,还需要尾插

(3) 要销毁哨兵位头节点

代码实现:

class Solution 
{
public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode* cur1 = l1, *cur2 = l2;ListNode* newHead = new ListNode(), *ptail = newHead; // 虚拟头节点int t = 0; // 记录进位while(cur1 || cur2 || t){if(cur1) {t += cur1->val;cur1 = cur1->next;}if(cur2) {t += cur2->val;cur2 = cur2->next;}ListNode* newNode = new ListNode(t % 10);ptail->next = newNode;ptail = newNode;ptail->next = nullptr;t /= 10;}ListNode* pcur = newHead->next;delete newHead;return pcur;}
};

24.两两交换链表中的节点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

本题也是一道模拟题,重点是画图,看图写代码!!! 定义四个指针
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dd888c8a2f4e4bf098ccdb8d4b4c7392.png
根据交换后的链表,进行指针的修改(绿色字),一直循环迭代
结束条件:
(1) 偶数个节点时:
cur 已经指向空了,next 和 nnext 也无效了,此时结束循环。
在这里插入图片描述
(2) 奇数个节点时:
next 已经指向空了, nnext 无效了,此时也结束循环。
在这里插入图片描述

代码实现:

class Solution 
{
public:ListNode* swapPairs(ListNode* head) {if(head == nullptr) return nullptr;ListNode* newHead = new ListNode(), *prev = newHead;ListNode* cur = head, *next = cur->next;ListNode* nnext = nullptr;if(next) nnext = next->next;prev->next = cur;while(cur && next){// 交换节点prev->next = next;next->next = cur;cur->next = nnext;// 修改指针prev = cur;cur = prev->next;if(cur) next = cur->next;if(next) nnext = next->next;}ListNode* pcur = newHead->next;delete newHead;return pcur;}
};

143.重排链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

这道题比较有综合性,通过分析可以发现:
重排后的链表 = 合并两个链表(原链表的前半段 + 原链表的后半段逆置)

在这里插入图片描述
所以这道题的思路是:
(1) 找到链表的中间节点
(2) 把后半部分逆序
(3) 合并两个链表

代码实现:

class Solution 
{
public:// 找中间节点ListNode* FindMiddleNode(ListNode* head){ListNode* slow = head, *fast = head, *prev = head;while(fast && fast->next){prev = slow;slow = slow->next;fast = fast->next->next;}prev->next = nullptr; // 与后半段断开return slow;}// 逆置后半部分链表ListNode* reverseList(ListNode* head){ListNode* phead = head, *ptail = nullptr;while(phead){ListNode* next = phead->next;// 头插法phead->next = ptail;ptail = phead;phead = next;}return ptail;}void reorderList(ListNode* head) {// 处理特殊情况if(head->next == nullptr || head->next->next == nullptr) return;ListNode* mNode = FindMiddleNode(head);ListNode* rNode = reverseList(mNode);// 合并两个链表ListNode* newHead = new ListNode(), *ptail = newHead;ListNode* n1 = head, *n2 = rNode;while(n1 && n2){ptail->next = n1;ptail = ptail->next;n1 = n1->next;ptail->next = n2;ptail = ptail->next;n2 = n2->next;}if(n1) ptail->next = n1;if(n2) ptail->next = n2;delete newHead;}
};

23.合并k个升序链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

解法1:暴力解法,利用"合并两个有序链表"的思想。先把第一个和第二个链表进行合并,得到一个新链表,再用新链表和第三个链表进行合并…。
假设有 k 个链表,每条链表的平均节点有 n 个,则时间复杂度为:n(k-1) + n(k-2) + n(k-3) + … + n --> O(n * k^2)。大概率超时

解法2:利用优先级队列进行优化
假设有 k 个链表,建一个大小为 k 的小根堆,把所有链表的第一个节点都扔进小根堆中,堆顶节点就是最小值,取出堆顶节点和虚拟头结点进行链接,再移到下一个节点…
合并链接+找出最小值的节点总的时间复杂度为:O(n * k * logk)

易错/细节问题:

(1) 优先级队列的第三个模板参数不能直接写成 greater<ListNode * >,因为这样比较的是地址,要写一个仿函数!!
(2) 把链表的所有头节点都入队列时,要注意传递的链表为空的处理

代码实现:

class Solution
{struct cmp{bool operator()(const ListNode* l1, const ListNode* l2){return l1->val > l2->val;}};
public:ListNode* mergeKLists(vector<ListNode*>& lists){// 建小根堆priority_queue<ListNode*, vector<ListNode*>, cmp> pq;// 把所有头结点进小根堆for (auto l : lists)if (l) pq.push(l);// 合并k个有序链表ListNode* newhead = new ListNode(), * ptail = newhead;while (!pq.empty()){ListNode* pcur = pq.top();pq.pop();ptail->next = pcur;ptail = pcur;if (pcur->next) pq.push(pcur->next);}ListNode* ret = newhead->next;delete newhead;return ret;}
};

解法3:分治-递归
和归并分治的思想一样:
在这里插入图片描述
假设有 k 个链表,那么就有 logk 层,递归回退时每个链表的每个节点都执行 logk 次合并,每个链表的平均节点有 n 个,所以时间复杂度为:O(n * k * logk)

易错/细节问题:

(1) 有两个递归出口:区间不存在和区间里只有一个链表
(2) 合并两个有序链表时,链表为空的情况

代码实现:

class Solution 
{
public:ListNode* mergeKLists(vector<ListNode*>& lists) {return merge(lists, 0, lists.size()-1);}ListNode* merge(vector<ListNode*>& lists, int left, int right){if(left > right) return nullptr;if(left == right) return lists[left];// 找中点int mid = left + (right - left) / 2;// [left, mid] [mid+1, right]// 合并左右两边ListNode* l1 = merge(lists, left, mid);ListNode* l2 = merge(lists, mid+1, right);// 合并两个有序链表return mergeTwoList(l1, l2);}ListNode* mergeTwoList(ListNode* l1, ListNode* l2){// 处理特殊情况if(l1 == nullptr) return l2;if(l2 == nullptr) return l1;ListNode head; // 把虚拟节点开在栈上head.next = nullptr;ListNode* ptail = &head;while(l1 && l2){if(l1->val < l2->val){ptail->next = l1;ptail = ptail->next;l1 = l1->next;}else{ptail->next = l2;ptail = ptail->next;l2 = l2->next;}}if(l1) ptail->next = l1;if(l2) ptail->next = l2;return head.next;}
};

25.k个一组翻转链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

这也是一道模拟题,算法流程比较简单
(1) 先求出需要逆序多少组:n
(2) 重复 n 次,长度为 k 的链表的逆序即可 。
(3) 把不需要逆序的接上。
在这里插入图片描述

细节问题:

(1) 这里需要记录第n次头插的开始位置,每次进行新一轮逆序时都要记住该位置。
(2) 如果使用while循环,则每逆序完一组后 k 要重新赋值,不然就一直都是k==0了

代码实现:

class Solution
{
public:ListNode* reverseKGroup(ListNode* head, int k){// 先求出要逆序多少组int n = 0;ListNode* phead = head;while (phead){phead = phead->next;n++;}n /= k;// 重复 n 次长度为 k 的链表逆序phead = head;ListNode* newhead = new ListNode(), * prev = newhead;for (int i = 0; i < n; i++){ListNode* tmp = phead; // 记录每一组的起点for (int j = 0; j < k; j++){ListNode* next = phead->next;phead->next = prev->next;prev->next = phead;phead = next;}prev = tmp;}// 把不需要翻转的接上prev->next = phead;ListNode* ret = newhead->next;delete newhead;return ret;}
};

三,算法总结

通过上面的若干道题目可以发现,大多数链表类的题目本质上是模拟题,最重要的就是画图,分清各个指针指向哪里。

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

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

相关文章

Linux命令大全及小例子

撰写一份关于Linux命令大全的详尽报道和分析是一项重要的任务&#xff0c;旨在让读者全面了解Linux命令的用途和应用场景。Linux系统因其强大的命令行工具而闻名&#xff0c;无论是系统管理、文件操作还是网络配置&#xff0c;Linux命令行都提供了灵活且强大的解决方案。以下是…

保险丝基础知识

一、简介 保险丝&#xff08;fuse&#xff09;也被称为电流保险丝&#xff0c;它能够在电流异常升高到一定的高度和热度时&#xff0c;自动熔断切断电流&#xff0c;从而保护电路安全运行。 IEC127标准将它定义为“熔断体&#xff08;fuse-link)”。熔断体是由电阻率比较大而熔…

初识Linux · 文件(1)

目录 前言&#xff1a; 回顾语言层面的文件 理解文件的预备知识 文件和磁盘 使用和认识系统调用函数 前言&#xff1a; 本文以及下篇文章&#xff0c;揭露的都是Linux中文件的奥秘&#xff0c;对于文件来说&#xff0c;初学Linux第一节课接触的就是文件&#xff0c;对于C…

在树莓派上部署开源监控系统 ZoneMinder

原文&#xff1a;https://blog.iyatt.com/?p17425 前言 自己搭建&#xff0c;可以用手里已有的设备&#xff0c;不需要额外买。这套系统的源码是公开的&#xff0c;录像数据也掌握在自己手里&#xff0c;不经过不可控的三方。 支持设置访问账号 可以保存录像&#xff0c;启…

《深入理解java虚拟机》——java内存区域与内存溢出异常

我是很喜欢用java语言编写代码的。从开始学习到现在其实也是在一步步体会java语言的各方面&#xff0c;开始看深入理解java虚拟机这本书觉得java虚拟机的内部感觉就像是一个操作系统&#xff0c;也可以说是个计算机。想要深入的理解我觉得需要先从整体去看。为什么需要java虚拟…

ProtoBuf快速上手

文章目录 创建 .proto文件编译 .proto文件编译后生成的文件序列化与反序列化的使用 此篇文章实现内容&#xff1a; 对一个通讯录的联系人信息&#xff0c;使用PB进行序列化&#xff0c;并将结果输出对序列化的内容使用PB进行反序列化&#xff0c;解析联系人信息并输出联系人信…

redis-数据类型

十大数据类型 学习 redis 操作手册 英文 Commands 中文 Redis命令中心&#xff08;Redis commands&#xff09; – Redis中国用户组&#xff08;CRUG&#xff09; 学习方法 举出一个数据结构的应用场景&#xff08;理解数据结构特点&#xff09;&#xff0c;并操作&…

【Java的SPI机制】Java SPI机制:实现灵活的服务扩展

在Java开发中&#xff0c;SPI&#xff08;Service Provider Interface&#xff0c;服务提供者接口&#xff09;机制是一种重要的设计模式&#xff0c;它允许在运行时动态地插入或更换组件实现&#xff0c;从而实现框架或库的扩展点。本文将深入浅出地介绍Java SPI机制&#xff…

JAVA开源项目 旅游管理系统 计算机毕业设计

本文项目编号 T 063 &#xff0c;文末自助获取源码 \color{red}{T063&#xff0c;文末自助获取源码} T063&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 用例设计 六、核…

TypeScript 封装 Axios 1.7.7

随着Axios版本的不同&#xff0c;类型也在改变&#xff0c;以后怎么写类型&#xff1f; yarn add axios1. 封装Axios 将Axios封装成一个类&#xff0c;同时重新封装request方法 重新封装request有几个好处&#xff1a; 所有的请求将从我们定义的requet请求中发送&#xff…

Vue3实现动态菜单功能

文章目录 0.效果演示1.搭建Vue3项目1.1 vite 脚手架创建 Vue3 项目1.2 设置文件别名1.3 安装配置 element-plus1.4 安装配置路由2.登录页面3.后台管理页面3.1 搭建后台框架3.2 左侧菜单栏3.3 header 用户信息3.4 主要内容3.5 footer4.配置静态路由5.记录激活菜单5.1 el-menu 绑…

信号处理快速傅里叶变换(FFT)的学习

FFT是离散傅立叶变换的快速算法&#xff0c;可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的&#xff0c;但是如果变换到频域之后&#xff0c;就很容易看出特征了。这就是很多信号分析采用FFT变换的原因。另外&#xff0c;FFT可以将一个信号的频谱提取出来&am…

webpack信息泄露

先看看webpack中文网给出的解释 webpack 是一个模块打包器。它的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换、打包或包裹任何资源。 如果未正确配置&#xff0c;会生成一个.map文件&#xff0c;它包含了原始JavaScript代码的映…

课设实验-数据结构-线性表-手机销售

题目&#xff1a; 代码&#xff1a; #include<stdio.h> #include<string.h> #define MaxSize 10 //定义顺序表最大长度 //定义手机结构体类型 typedef struct {char PMod[10];//手机型号int PPri;//价格int PNum;//库存量 }PhoType; //手机类型 //记录手机的顺序…

【HTTP(3)】(状态码,https)

【认识状态码】 状态码最重要的目的&#xff0c;就是反馈给浏览器:这次请求是否成功&#xff0c;若失败&#xff0c;则出现失败原因 常见状态码: 200:OK&#xff0c;表示成功 404:Not Found&#xff0c;浏览器访问的资源在服务器上没有找到 403:Forbidden&#xff0c;访问被…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…

【案例】距离限制模型透明

开发平台&#xff1a;Unity 2023 开发工具&#xff1a;Unity ShaderGraph   一、效果展示 二、路线图 三、案例分析 核心思路&#xff1a;计算算式&#xff1a;透明值 实际距离 / 最大距离 &#xff08;实际距离 ≤ 最大距离&#xff09;   3.1 说明 | 改变 Alpha 值 在 …

stm32f103调试,程序与定时器同步设置

在调试定时器相关代码时&#xff0c;注意到定时器的中断位总是置1&#xff0c;怀疑代码有问题&#xff0c;经过增大定时器的中断时间&#xff0c;发现定时器与代码调试并不同步&#xff0c;这一点对于调试涉及定时器的代码是非常不利的&#xff0c;这里给出keil调试stm32使定时…

自用Proteus(8.15)常用元器件图示和功能介绍(持续更新...)

文章目录 一、 前言二、新建工程&#xff08;以51单片机流水灯为例&#xff09;2.1 打开软件2.2 建立新工程2.3 创建原理图2.4 不创建PCB布版设计2.5 创建成功2.6 添加元器件2.7 原理图放置完成2.8 编写程序&#xff0c;进行仿真2.9 仿真 三、常用元器件图示和功能介绍3.1 元件…

【回眸】Tessy 单元测试软件使用指南(四)常见报错及解决方案与批量初始化的经验

前言 分析时Tessy的报错 1.fatal error: Tricore/Compilers/Compilers.h: No such file or directory 2.error: #error "Compiler unsupported" 3.warning: invalid suffix on literal;C11 requires a space between literal and string macro 4.error: unknown…