【链表】链表类型题目常用技巧及例题
个人主页 : 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)方便对链表操作
链表类的相关算法题,一般是不带头的单向链表,从第一个节点开始就开始存储有效数据,就会出现很多边界情况需要考虑。此时就可以重新创建一个虚头结点,它去链接题目中的链表。 -
不要吝啬空间,大胆去定义变量
-
快慢双指针
判环
找链表中环的入口
找链表中倒数第n个节点
链表中的常用操作
- 创建一个新节点
- 尾插
- 头插
方便逆序链表
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 分析
算法:模拟
-
找到链表的中间节点
快慢双指针 -
把后半部分逆序
头插,三指针(双指针) -
合并两个链表
双指针
-
找到链表的中间节点-》快慢双指针(考虑slow的落点在哪里)
奇数情况:slow刚好在中间位置,此时逆序位置从slow->next开始
偶数情况:slow在中间靠后位置,此时逆序位置可以从slow->next开始,也可以从slow开始。
从slow->next开始,偶数的话,正中间的位置在重排后仍然是在一起的,就可以把slow位置节点放在前半段节点中。
如果偶数情况从slow开始,就得重新写一段代码,与前半段不一样,就比较麻烦。
- 把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 分析
算法:模拟
- 算出需要逆序多少组
- 重复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;}
};
有问题请指出,大家一起进步!!!