【ps】本篇有 5 道 leetcode OJ。
一、算法简介
链表是一种常见的线性数据结构,是一种在物理结构上非连续、非顺序的存储结构,其中的数据元素的逻辑顺序由其中的指针链接次序实现,指针链接的每一个结构体都是一个节点。
链表的结构多种多样,有单向或双向、带头或不带头、循环或非循环之分。但实际中,最常用的是以下两种组合:
- 无头单向非循环链表(或称单链表)
- 带头双向循环链表(或称双向链表)
【Tips】链表的常用技巧
- 一定要画图!
- 引入一个虚拟的头节点,利于处理边界情况、对链表进行各种操作。
- 不要吝啬空间,大胆定义变量去使用,尤其涉及到插入操作的时候。
- 快慢双指针,可以对链表判环、找环的入口、找链表中倒数第 n 个节点。
【Tips】链表的常见操作
- 创建一个新节点
- 尾插
- 头插
二、相关例题
1)两数相加
2. 两数相加
.1- 题目解析
要将两个链表的每个节点中的数相加并放在一个新的链表中,那一定就要构建一个链表,我们可以对这个新链表引入一个头节点,然后直接遍历两个链表进行求和,然后将结果构建出一个新节点尾插入头节点即可。
特别的,我们单独定义一个变量 t,既保存相加的结果,又记录进位。
.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* newhead=new ListNode(0);ListNode*cur1=l1,*cur2=l2,*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);//取结果的个位t/=10; //取进位prev=prev->next;}prev=newhead->next;delete newhead;return prev;}
};
2)两两交换链表中的节点
24. 两两交换链表中的节点
.1- 题目解析
以示例 1 为例。我们可以引入一个头节点,并定义四个指针变量来表示不同的节点。
由此,要实现题目要求的交换,仅需交换 cur 和 next 指向的节点即可,然后将这四个指针整体向后移动,继续完成下次交换,直到 cur 遇到了空节点,说明此时整个链表都完成了交换。
.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) {if(head==nullptr||head->next==nullptr)return head; //处理边界情况ListNode* newhead=new ListNode(0);newhead->next=head;ListNode* prev=newhead,*cur=newhead->next;ListNode* next=cur->next,*nnext=next->next;while(cur && next){//交换 cur 和 nextcur->next=nnext;next->next=cur;prev->next=next;//继续遍历链表prev=cur;cur=nnext;if(cur)next=cur->next;if(next)nnext=next->next;}cur=newhead->next;delete newhead;return cur;}
};
3)重排链表
重排链表. - 力扣(LeetCode)
.1- 题目解析
以示例 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:void reorderList(ListNode* head) {if(head==nullptr||head->next==nullptr||head->next->next==nullptr)return;//找到原始链表的中间位置ListNode* fast=head,*slow=head;while(fast&&fast->next){slow=slow->next;fast=fast->next->next;}//头插法逆序后半部分ListNode* newhead=new ListNode(0);ListNode*cur=slow->next,*next=cur->next;slow->next=nullptr; //断开前后两个链表while(cur){next=cur->next;cur->next=newhead->next;newhead->next=cur;cur=next; }//合并两个链表ListNode* ret=new ListNode(0);ListNode* prev=ret;ListNode* cur1=head,*cur2=newhead->next;while(cur1){prev->next=cur1;cur1=cur1->next;prev=prev->next;if(cur2){prev->next=cur2;cur2=cur2->next;prev=prev->next;}}delete newhead;delete ret;}
};
4)合并 K 个升序链表
23. 合并 K 个升序链表
.1- 题目解析
要合并 K 个升序链表,可以先合并两个升序链表,再继续两两合并剩下的链表。我们可以用优先级队列(建小根堆排升序)来优化这个过程,将所有链表的头节点依此入队,即可找到最小的那个节点,只需将其尾插入一个新的头节点,然后将后续的节点入队,重复这个过程,就可以完成 K 个升序链表的合并了。
另外,先合并两个升序链表,再继续两两合并剩下的链表,也可以用归并的方式来解决。
.2- 代码编写
//法1:优先级队列
/*** 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);//合并链表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;}
};
//法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* 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;//递归处理左右区间ListNode* l1=merge(lists,left,mid);ListNode* l2=merge(lists,mid+1,right);//合并两个有序链表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->next=cur1;prev=cur1;cur1=cur1->next;}else{prev->next=cur2;prev=cur2;cur2=cur2->next;}}if(cur1)prev->next=cur1;if(cur2)prev->next=cur2;return head.next;}
};
5)K 个一组翻转链表
25. K 个一组翻转链表
.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* 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;}
};