算法——链表

链表常用技巧

  1. 画图分析!!!!!!!!!!——直观形象,便于理解、大多数都是模拟
  2. 引入虚拟头结点(哨兵位)
    1. 典型的就是在第一个节点传空指针,此时我们如果解引用,程序直接崩掉

image.png

  1. 我们一般选择创建哨兵位头结点,这里我们哨兵位不存储数据。解释传入空链表,这里的newhead只需要特判一下即可image.png

  2. 不要吝啬空间,大胆定义变量。

    1. 我们经常会遇到这种题目,将cur插入到prev后面。途中四句是正确的顺序,在考试题目中经常会遇到将这四句顺序打乱的情况。其实我们只需要保证前两句先执行即可(五角星标志的),这样是为了防止prev找不到next。如果先执行后两句,那么prev链接cur之后,就找不到next节点

image.png

  2. 我们也可以定义next指针,这样就不需要考虑下面四句语句的执行顺序

image.png

  1. 链表快慢双指针,典型题目有:判环;找链表中环的入口;找链表中倒数第n个节点

链表中常用操作

  1. 创建一个新节点 new
  2. 尾插:先定义一个指针tail指向最后一个位置,让tail->next 指向新节点,然后将尾结点转移到新节点上。image.png
  3. 头插:让新节点的next指向newhead->next,此时如果之前给的链表为空,让新节点next指向空,不会发生访问空节点的报错。然后让newhead->next指向新节点。如果怕链表断开,可以新定义一个next指针。我们也可以利用这个方法完成逆序链表

image.pngimage.png

两数相加

两数相加

题目解析

image.png
这里给我们逆序存储链表反而有利于我们做题,我们相加时是从最低位开始加,如果题里给我们正常顺序存储的链表的话,我们还需要自己逆置。

算法原理

解法:模拟两数相加的过程

  • 这里创建一个newhead用于链接最终结果。如果这里我们不用newhead链接,我们需要先把两个链表头结点相加然后存储新的head用来记录结果(因为最后要返回链表),然后再继续往后相加。这样我们操作就多了一步。

image.png

  • 用一个变量t来标记我们的进位,然后定义两个指针模拟加法。只要cur1、cur2不为空,就把他们指向的数字移到t中,然后把t中个位数字提出来(因为数字相加可能变为两位数,牵扯到进位),然后把new一个节点7,链接到到我们的newhead后面。同理…

image.pngimage.png

代码实现

/*** Definition for singly-linked list.* 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) {}* };*/
class Solution {
public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode* cur1 = l1, *cur2 = l2;ListNode* newhead = new ListNode(0); // 创建⼀个虚拟头结点,记录最终结果ListNode* prev = newhead; // 尾指针int t = 0; // 记录进位
while(cur1 || cur2 || t)
{// 先加上第⼀个链表if(cur1){t += cur1->val;cur1 = cur1->next;}// 加上第⼆个链表if(cur2){t += cur2->val;cur2 = cur2->next;}prev->next = new ListNode(t % 10);prev = prev->next;t /= 10;
}prev = newhead->next;delete newhead;return prev;}
};

两两交换链表中的节点

两两交换链表中的节点

题目解析

  • 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

image.png

算法原理

解法:循环迭代:

  • 这里同样引入一个虚拟节点newhead链接链表。如果不引入头结点,在我们交换前两个节点的时候,因为前面没有节点。所以我们要先定义一个变量找到最终要返回的头结点。因为前半部分1、2两个节点是不需要将修改将前面的指针指向2,但是后面部分3、4需要交换过后,让前面位置的指针指向4链接。这时需要写两段代码分别写两种情况。image.png
  • 不要吝啬我们的空间,定义四个指针,紫色情况是交换1、2,绿色情况是交换3、4image.png
  • 当3、4交换完成时,四个指针移动如下情况:
    • 当链表是奇数个节点,这时cur有可能访问空指针,所以我们需要终止循环image.png
    • 当链表是偶数个节点时,next指向空,需要终止循环。image.png

所以终止循环条件:cur或者next有一个指向空的时候,就终止循环

代码实现

/*** Definition for singly-linked list.* 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) {}* };*/
class Solution {
public:ListNode* swapPairs(ListNode* head) {if(head == nullptr || head->next == nullptr) return head;ListNode* newHead = new ListNode(0);newHead->next = head;ListNode* prev = newHead, *cur = prev->next, *next = cur->next, *nnext =next->next;while(cur && next){
// 交换结点prev->next = next;next->next = cur;cur->next = nnext;
// 修改指针prev = cur; // 注意顺序cur = nnext;if(cur) next = cur->next;if(next) nnext = next->next;}cur = newHead->next;delete newHead;return cur;}
};

重排链表

重排链表

题目解析

  • 第一个、倒数第一个、第二个、倒数第二个image.png

算法原理

**解法:模拟——**其实很像两个链表合并,一个是正向的1、2,一个是倒向的链表4、3。恰好是原始链表的前半部分,和后半部分逆序。

所以我们的策略是:先找到链表的中点,然后把后面部分的链表逆序;然后把前面的部分和后面的部分一个一个交互链接,合并一起即可。image.png

  1. 找到链表的中间节点(⼀定要画图考虑 slow 的落点在哪⾥ )

    1. 利用快慢双指针。这里注意一下节点个数是奇数和偶数的情况。slow=slow->next;fast=fast->next->next;我们的停止循环的条件是当fast或fast->next为空。如下图分别是节点个数为奇数和偶数的情况。此时观察slow落在中点的情况:
      1. 我们可以让slow->next部分直接翻转。这里我们可能会有疑惑,难道不应该让slow后面的部分整体翻转吗?这是这道题的特殊性,我们还是用1234进行举例子,我们发现重排前和重拍后发现2和3的链接部分是不变的,所以我们可以把slow指向的这个节点大胆给第一个链表。重排后发现是不影响结果的。

image.png

  2. 我们也可以让slow所指的开始翻转(红色笔标注的)。此时无非是把slow所指的节点丢给后半部分,逆序合并后发现结果并不影响![image.png](https://img-blog.csdnimg.cn/img_convert/77049b1c441cb9b3336de5f341cee1c9.png)
  1. 用头插法,如果这里使用头插法,建议使用第一个方法(slow->next翻转)。因为最终是断开成两个链表,
    1. 如果从slow所指的位置开始逆置,会发现箭头所指的位置指针无法断开。有可能出现意想不到的错误;

image.png

  2. 当我们选择slow->next位置开始翻转,我们只需让slow所指的位置置空即可,

image.png

  3. 如果我们只能用slow位置开始翻转的情况时,我们可以引入一个虚拟头结点newhead(哨兵位),去链接head,然后在快慢指针找中间节点,之前是slow位置,就会变成slow->next,这时候也能将两个链表断开。
  1. 把slow后面部分逆序——头插法(使用虚拟头结点)
    • 把slow后面的节点,头插到新建的头(哨兵位)结点后面。当让cur头插到newhead后面时,先让cur->next指向空(就是newhead2后面的节点),然后newhead2->next指向cur,此时newhead2就与null(原先后面的节点)断开
    • 当newhead2后面有节点时,按照上面的操作我们会发现,newhead2会与后面的断开了,所以在刚开始头插时我们要定义一个next指针标记newhead2后面的节点。当执行完上面操作后,让cur=nextimage.png
  2. 合并两个链表
    1. 我们是创建一个虚拟节点,让他们都尾插在这后面
    2. cur1相较于cur2会长一些,所以循环判空条件为cur1为空。

代码实现

/*** Definition for singly-linked list.* 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) {}* };*/
class Solution {
public:void reorderList(ListNode* head) {// 处理边界情况if(head == nullptr || head->next == nullptr || head->next->next == nullptr){return ;}// 1. 找到链表的中间节点 - 快慢双指针(⼀定要画图考虑 slow 的落点在哪⾥)ListNode* slow = head, *fast = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;}// 2. 把 slow 后⾯的部分给逆序 - 头插法ListNode* head2 = new ListNode(0);ListNode* cur = slow->next;slow->next = nullptr; // 注意把两个链表给断开   while(cur){ListNode* next = cur->next;cur->next = head2->next;head2->next = cur;cur = next;}// 3. 合并两个链表 - 双指针ListNode* ret = new ListNode(0);ListNode* prev = ret;ListNode* cur1 = head, *cur2 = head2->next;while(cur1){
// 先放第⼀个链表prev->next = cur1;cur1 = cur1->next;prev = prev->next;// 再放第⼆个链表if(cur2){prev->next = cur2;prev = prev->next;cur2 = cur2->next;}}
delete head2;
delete ret;
}
};

合并k个升序链表

合并k个升序链表

题目解析

image.png

算法原理

**解法一:暴力解法:**逐个合并.其中n是链表的长度,k为链表的个数image.png

解法二:优先级队列做优化
我们可以直接定义k个指针,仿照合并k个有序链表,把这些链表中较小的头结点放到newhead后面,放完之后右移,继续比较各个节点最小的,把最小的连接在newhead后面即可。这样只需要遍历一遍链表即可。O(n*k)是理想状态下的时间复杂度,我们还要找出k个节点中最小的节点。这里我们可以利用优先级队列

把k个节点丢入小根堆里,经过排序后,堆顶元素即为最小的元素,链接到newhead后面后,将移动后下一个节点放入小根堆里。这里时间复杂度O(nklogk)
image.png

解法三:分治——递归
流程:

  1. 特判,如果题⽬给出空链表,⽆需合并,直接返回;
  2. 返回递归结果。


递归函数设计:

  1. 递归出⼝:如果当前要合并的链表编号范围左右值相等,⽆需合并,直接返回当前链表;
  2. 应⽤⼆分思想,等额划分左右两段需要合并的链表,使这两段合并后的⻓度尽可能相等;
  3. 对左右两段分别递归,合并[l,r]范围内的链表
  4. 再调⽤mergeTwoLists函数进⾏合并(就是合并两个有序链表)

时间复杂度:一共k个链表,每个链表里面有n个节点,每一层我们都平均分,即高度为logk,每一个节点都执行了logk次合并(这棵树的高度次)image.png

代码实现

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> heap;// 让所有的头结点进⼊⼩根堆for(auto l : lists)if(l) heap.push(l);// 合并 k 个有序链表
ListNode* ret = new ListNode(0);
ListNode* prev = ret;
while(!heap.empty()){ListNode* t = heap.top();heap.pop();prev->next = t;prev = t;if(t->next) heap.push(t->next);}prev = ret->next;
delete ret;
return prev;
}
};
/*** Definition for singly-linked list.* 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) {}* };*/
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];// 1. 平分数组int mid = left + right >> 1;// [left, mid] [mid + 1, right]// 2. 递归处理左右区间ListNode* l1 = merge(lists, left, mid);ListNode* l2 = merge(lists, mid + 1, right);// 3. 合并两个有序链表return mergeTowList(l1, l2);}ListNode* mergeTowList(ListNode* l1, ListNode* l2){if(l1 == nullptr) return l2;if(l2 == nullptr) return l1;// 合并两个有序链表ListNode head;ListNode* cur1 = l1, *cur2 = l2, *prev = &head;head.next = nullptr;while(cur1 && cur2){if(cur1->val <= cur2->val){prev = prev->next = cur1;cur1 = cur1->next;}else{prev = prev->next = cur2;cur2 = cur2->next;}}if(cur1) prev->next = cur1;if(cur2) prev->next = cur2;return head.next;}
};

k个一组翻转链表

k个一组翻转链表

题目解析

链表中的节点每k个一组进行翻转,如果剩余的链表不够k个,则不翻转image.png

算法原理

解法:模拟

  1. 先求出需要逆序多少组n:遍历链表一遍 n=链表节点个数/k
  2. 重复n次长度为k的链表的逆序
    1. 利用头插法逆序,创建一个newhead。头插时,cur->next=head->next;head->next = cur;此时右边节点(2)会丢失,需要先用一个next指针记录一下节点,然后头插。头插完之后cur++,直至头插3之后,这一组完成。

image.png

  1. 进行下一组头插时,需要将4头插到1的后面,所以需要一个指针标记一下1的位置,这个位置是每一组开始头插的第一个位置。所以每组第一个位置开始头插的时候,要标记好开始头插的位置(tmp),还需要定义个prev标记将要头插位置的前驱,每当一组头插完,该进行下一组时,让prev更新到tmp位置。image.pngimage.png

代码实现

/*** Definition for singly-linked list.* 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) {}* };*/
class Solution {
public:ListNode* reverseKGroup(ListNode* head, int k) {// 1. 先求出需要逆序多少组int n = 0;ListNode* cur = head;while(cur){cur = cur->next;n++;}n /= k;// 2. 重复 n 次:⻓度为 k 的链表的逆序即可ListNode* newHead = new ListNode(0);ListNode* prev = newHead;cur = head;for(int i = 0; i < n; i++){ListNode* tmp = cur;for(int j = 0; j < k; j++){ListNode* next = cur->next;cur->next = prev->next;prev->next = cur;cur = next;}prev = tmp;}// 把不需要翻转的接上prev->next = cur;cur = newHead->next;delete newHead;return cur;}
};

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

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

相关文章

IDEA的JavaWeb项目的webapp目录的小蓝点没亮怎么办???

我们可以看到没有小蓝点&#xff0c;正常的是有的。 没有小蓝点的原因&#xff1a;你的webapp 这个文件夹的路径不对。 解决方案&#xff1a;&#xff08;修改webapp的路径&#xff09; 我们可以看到&#xff0c;错误的路径是显示红色。 这个地方没有就添加一个路径&#xff0…

广东mes生产管理系统

mes生产管理系统是面向制造企业执行层的生产信息化管理系统。它可以帮助企业实现制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理、人力资源管理、工作与设备管理、工具工装管理、采购管理、成本管理、项目看板管理、生产过程控制、底层数据集成分析、上层数据集…

处理etcd源码包编译异常

1、下载etcd包&#xff0c;执行go build报异常&#xff1a; client\v2\example_keys_test.go:1:1: expected package, found . client\v3\example_auth_test.go:1:1: expected package, found . client\v3\concurrency\example_election_test.go:1:1: expected package, found…

挖到宝了,大数据分析工具做分析真的太快了

随着企业越做越大&#xff0c;累积数据的速度越来越快&#xff0c;但分析的效率却不升反降&#xff0c;不利于数字化运营决策。但大数据分析工具的出现让这一现象成为过去&#xff0c;无他&#xff0c;就是大数据分析工具做分析的真的太快了&#xff0c;可在任意终端上随时按需…

什么是流媒体

所谓的流媒体是指将一连串的媒体数据压缩后&#xff0c;经过网络分段发送数据&#xff0c;在网上即时传输音视频以供观赏的一种技术。如果没有流媒体技术&#xff0c;那么在观赏前必须下载整个媒体文件。 常见流媒体协议&#xff1a; RTSPRTPHTTPHLSRTMPHTTP-FLV等 目前应用…

欧洲:数百家电商网站泄露了用户支付信息

欧洲刑警组织在12月22日发布的新闻稿中&#xff0c;称由 17 个国家联合参与的执法行动已发现数百个电商平台存在恶意脚本攻击&#xff0c;其用户的信用卡或支付卡数据已遭到泄露。 这项已进行两个月的执法行动由希腊牵头&#xff0c;欧洲刑警组织参与协调&#xff0c;并得到网络…

《运维人员的未来:IT界的“万金油“如何继续闪耀光芒》

文章目录 每日一句正能量前言35岁被称为运维半衰期&#xff0c;究竟为何&#xff1f;如何顺利过渡半衰期运维的职业发展路径后记 每日一句正能量 凡事顺其自然&#xff0c;遇事处于泰然&#xff0c;得意之时淡然&#xff0c;失意之时坦然&#xff0c;艰辛曲折必然&#xff0c;历…

AIGC|什么是深度学习?

深度学习是近年来人工智能领域最热门的话题之一。它是一种通过模拟人脑神经网络工作原理&#xff0c;进行大规模数据处理和模式识别的机器学习方法。随着计算能力的提升和大数据时代的到来&#xff0c;深度学习在图像识别、语音识别、自然语言处理等领域取得了突破性进展&#…

浮点数的转换--IEEE 754

IEEE754标准是一种浮点数表示标准&#xff0c;一般分为 单精度&#xff08;32位的二进制数&#xff09;&#xff1b;双精度&#xff08;64位的二进制数&#xff09; 根据国际标准IEEE754&#xff0c;任意一个二进制浮点数V可以表示为下面形式&#xff1a; V (-1)^s *&#…

【技术分享】单网口远程透传网关快速实现昆仑通态触摸屏程序远程上下载及监控

准备工作 一台可联网操作的电脑一台单网口的远程透传网关及博达远程透传配置工具网线一条&#xff0c;用于实现网络连接和连接触摸屏一台昆仑通态触摸屏及其编程软件一张4G卡或WIFI天线实现通讯(使用4G联网则插入4G SIM卡&#xff0c;WIFI联网则将WIFI天线插入USB口&#xff0…

python的import功能,你需要了解的导入机制

1 基本概念 概念 解释 import 即导入&#xff0c;方式就是在import位置将代码文件拷贝过去。 模块&#xff08;module&#xff09; python中一个.py文件定义为一个模块 常用的几种模块导入方式&#xff1a; import module_nameimport module_name.function_nameimport mod…

AI Earth平台简介

AI Earth地球科学云平台由达摩院-视觉技术实验室打造&#xff0c;基于地球科学智能计算分析方面的创新研究&#xff0c;致力于解决地球科学领域基础性、前沿性、业务性问题&#xff0c;目标成为国内一流的地球科学云计算平台。&#xff08;摘自官网&#xff09; 下面&#xff…