当前位置: 首页 > news >正文

【链表】链表类型题目常用技巧及例题

个人主页 : zxctscl
专栏 【C++】、 【C语言】、 【Linux】、 【数据结构】、 【算法】
如有转载请先通知

题目

  • 前言
    • 常用技巧
    • 链表中的常用操作
  • 1. 2. 两数相加
    • 1.1 分析
    • 1.2 代码
  • 2. 24. 两两交换链表中的节点
    • 2.1 分析
    • 2.2 代码
  • 3. ==143. 重排链表(常考)==
    • 3.1 分析
    • 3.2 代码
  • 4. 23. 合并 K 个升序链表
    • 4.1 分析
    • 4.2 代码
  • 5. 25. K 个一组翻转链表
    • 5.1 分析
    • 5.2 代码

前言

常用技巧

  1. 画图 ->直观+形象+便于我们理解

  2. 引入虚拟头结点->(1)便于处理边界情况 (2)方便对链表操作
    链表类的相关算法题,一般是不带头的单向链表,从第一个节点开始就开始存储有效数据,就会出现很多边界情况需要考虑。此时就可以重新创建一个虚头结点,它去链接题目中的链表。

  3. 不要吝啬空间,大胆去定义变量

  4. 快慢双指针
    判环
    找链表中环的入口
    找链表中倒数第n个节点

链表中的常用操作

  1. 创建一个新节点
  2. 尾插
  3. 头插
    方便逆序链表

1. 2. 两数相加

在这里插入图片描述

1.1 分析

这个就是模拟两个数相加的过程,给的两个非空链表是逆序的。
在这里插入图片描述

算法原理:模拟两数相加过程即可
就是拿两个数做加法
用虚拟头节点newnode来链接最终的结果,如果不创建虚拟头节点的话,就得先题目给的两个链表节点值的和来创建第一个节点,后面相加的节点再链接到后面。
在这里插入图片描述

加法得考虑到进位,用一个变量t来记录一下。
定义两个变量cur1,cur2,分别遍历两个链表,两个变量值的和记录到t中,再将t模10取余的值链接到newnode里面;再更新t的值为t/10,一直模拟这个过程。
在这里插入图片描述

1.2 代码

/*** 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* newnode=new ListNode(0);ListNode* tail=newnode;//记录新链表的尾指针int t=0;while(cur1||cur2||t){if(cur1){t+=cur1->val;cur1=cur1->next;}if(cur2){t+=cur2->val;cur2=cur2->next;}tail->next=new ListNode(t%10);tail=tail->next;t/=10;}tail =newnode->next;delete newnode;return tail;}
};

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

在这里插入图片描述

2.1 分析

用循环、迭代来解决

要将链表中的节点两两交换
在这里插入图片描述
此时要引入一个虚拟头结点,不引入头结点,在交换前面两个节点和后面的节点,逻辑是不一样的;前面1,和2两个交换完就是2、1;要指向后面交换后节点4和3,要用前面位置指针指向4。
在这里插入图片描述
此时要处理前面两个节点,就得和后面节点处理不同,就得写两段代码。
所以这里就引入虚拟头节点,后面交换的逻辑就相同了。

定义四个指针,这样方便处理,前面两个交换完,指针后移就行
此时代码就很容易编写
在这里插入图片描述

如果是偶数个节点,cur为空就结束循环;
如果是奇数个节点,next为空就结束循环;
在这里插入图片描述

2.2 代码

/*** 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) {ListNode* newhead=new ListNode(0);newhead->next=head;if(head==nullptr||head->next==nullptr)return 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;}
};

3. 143. 重排链表(常考)

在这里插入图片描述

3.1 分析

算法:模拟

  1. 找到链表的中间节点
    快慢双指针

  2. 把后半部分逆序
    头插,三指针(双指针)

  3. 合并两个链表
    双指针
    在这里插入图片描述
    在这里插入图片描述

  4. 找到链表的中间节点-》快慢双指针(考虑slow的落点在哪里)
    奇数情况:slow刚好在中间位置,此时逆序位置从slow->next开始
    偶数情况:slow在中间靠后位置,此时逆序位置可以从slow->next开始,也可以从slow开始。
    在这里插入图片描述
    从slow->next开始,偶数的话,正中间的位置在重排后仍然是在一起的,就可以把slow位置节点放在前半段节点中。
    在这里插入图片描述

如果偶数情况从slow开始,就得重新写一段代码,与前半段不一样,就比较麻烦。

  1. 把slow后面部分逆序
    用头插法,注意把两部分链表断开,只需要把sloe->next置空就行
    cur在头插之前,得先记录它后面的节点,防止节点丢失
    在这里插入图片描述
    在这里插入图片描述

3.2 代码

/*** 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.后半部分逆序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)//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;}
};

4. 23. 合并 K 个升序链表

在这里插入图片描述

4.1 分析

解法一:暴力解法
按照合并两个有序链表的逻辑,先把前两个合并成一个,再往后继续合并,这样会导致时间负责度高,达到三次方的程度:
在这里插入图片描述

解法二:利用优先级队列(简单)
利用小根队,将k个链表中所有第一个值插入,再插入中小根堆中,取出堆顶元素插入到新申请的链表中;再继续将链表中的值插入到小根堆中,直到k个链表中的值全部插入到新链表中,最后返回新链表头结点的next。
在这里插入图片描述
在这里插入图片描述

解法三:分治—递归

在这里插入图片描述

在这里插入图片描述

4.2 代码

解法二:

/*** 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 {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()){auto 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];//平分数组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 mergeKLists(l1,l2);}ListNode* mergeKLists(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;}
};

5. 25. K 个一组翻转链表

在这里插入图片描述

5.1 分析

算法:模拟

  1. 算出需要逆序多少组
  2. 重复n次,长度为k的链表的逆序即可。
    利用头插法逆序,申请一个虚拟头结点,先头插k个节点,再去头插下一组节点。但是下一组节点不是插入到head后面一个,而是在第一组的后面,用一个指针tmp来记录每一组的头结点。当插入下一组的的时候,用prev来记录排好序后的最后一个节点。
    在这里插入图片描述
    在这里插入图片描述

5.2 代码

/*** 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;}
};

有问题请指出,大家一起进步!!!

相关文章:

  • Resilience4j与Spring Cloud Gateway整合指南:构建弹性的API网关
  • Stable Diffusion 图像生成 GUI 应用:图像缩放等五个优化——SD界面学习记录
  • yml文件上传并映射到实体类
  • Service生命周期
  • 微图4在《宁夏清水湾小流域治理》工程项目中的应用案例
  • Arrays.asList()的对象不能调用add/remove/clear方法
  • 力扣-hot100(最长连续序列 - Hash)
  • C++高级2 智能指针
  • CodeBuddy 焕新升级: 软件开发智能体 Craft 重磅发布
  • Function Calling 与 RAG的关系
  • [第十六届蓝桥杯 JavaB 组] 真题 + 经验分享
  • MySQL 数据库备份和恢复全指南
  • 多线程(三)
  • Python Pandas实现导出两个Excel数据集的分组记录数分析
  • 封装一个搜索区域 SearchForm.vue组件
  • 【项目】构建高性能多线程内存池:简化版 tcmalloc 实现指南
  • scoped+组件通信+props校验+记事本组件
  • 企业微信PC端 开启调试模式
  • 关于我的服务器
  • Python + 淘宝 API 开发实战:自动化采集商品详情与 SKU 数据清洗指南
  • 网上销售假冒片仔癀和安宫牛黄丸,两人被判刑
  • 匈牙利总理投票反对乌克兰加入欧盟
  • 大连万达商业管理集团提前兑付“22大连万达MTN001” ,本息2.64亿元
  • 2025中国互联网企业家座谈会在京召开
  • 专访|松重丰:“美食家”不孤独,他在自由地吃饭
  • 尹锡悦涉嫌发动内乱案第二次庭审21日举行,媒体获准拍摄