算法沉淀——链表
- 01.两数相加
- 02.两两交换链表中的节点
- 03.重排链表
- 04.合并 K 个升序链表
- 05.K个一组翻转链表
链表常用技巧
1、画图->直观形象、便于理解
2、引入虚拟"头节点"
3、要学会定义辅助节点(比如双向链表的节点插入)
4、快慢双指针(判断链表是否有环、找到环的入口、找链表中倒数第n个节点等)
链表常用操作
1、创建新节点
2、头插(比如逆序链表)
3、尾插
01.两数相加
题目链接:https://leetcode.cn/problems/add-two-numbers/
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
- 每个链表中的节点数在范围
[1, 100]
内 0 <= Node.val <= 9
- 题目数据保证列表表示的数字不含前导零
思路
这里因为本身就是逆序的链表,其实是方便我们进行计算的,我们只需要一个临时变量来记录进位的值即可,然后我们可以正常按照计算规则逐个相加直至链表结束即可。
/**
* 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();
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;
}
};
02.两两交换链表中的节点
题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]
内 0 <= Node.val <= 100
思路
我们可以先创造一个头节点,其次就是画图用原链表和交换后的链表进行对比找规律,利用链表的结点指针来进行交换,找出结束循环的条件即可。
代码
/**
* 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||!head->next) return head;
ListNode* newhead=new ListNode();
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=nnext->next;
if(next) nnext=next->next;
}
prev=newhead->next;
delete newhead;
return prev;
}
};
03.重排链表
题目链接:https://leetcode.cn/problems/reorder-list/
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
提示:
- 链表的长度范围为
[1, 5 * 104]
1 <= node.val <= 1000
思路
我们画图发现可以先使用快慢指针的方式找到中间节点,然后使用头插法将后半部分逆序,再逐个拼接两段链表即可得到所需要的链表
代码
/**
* 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||!head->next||!head->next->next) return;
ListNode* slow=head,*fast=head;
while(fast&&fast->next){
slow=slow->next;
fast=fast->next->next;
}
ListNode* newhead=new ListNode();
ListNode* cur=slow->next;
slow->next=nullptr;
while(cur){
ListNode* next=cur->next;
cur->next=newhead->next;
newhead->next=cur;
cur=next;
}
ListNode* ret=new ListNode();
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;
}
};
04.合并 K 个升序链表
题目链接:https://leetcode.cn/problems/merge-k-sorted-lists/
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
思路1
这里最简单也是比较高效的一种写法,就是使用一个小根堆,将这k个链表的头节点放进去,逐个将堆顶的最小链表节点拼接,再逐个入堆,最后就可以将k个有序链表完成拼接
代码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 comp{
bool operator()(const ListNode* l1,const ListNode* l2){
return l1->val>l2->val;
}
};
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,comp> heap;
for(auto li:lists) if(li) heap.push(li);
ListNode* ret=new ListNode();
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;
}
};
思路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 mergeTlist(l1,l2);
}
ListNode* mergeTlist(ListNode* l1,ListNode* l2){
if(l1==nullptr) return l2;
if(l2==nullptr) return l1;
ListNode* head=new ListNode();
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;
}
};
05.K个一组翻转链表
题目链接:https://leetcode.cn/problems/reverse-nodes-in-k-group/
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
提示:
- 链表中的节点数目为
n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
思路
这里我们可以先计算要翻转的子链表个数,也就是要反转的次数,再使用头插法逐个翻转拼接即可
代码
/**
* 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) {
int n=0;
ListNode* cur=head;
while(cur){
cur=cur->next;
n++;
}
n/=k;
ListNode* newhead=new ListNode();
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;
}
};