文章目录
前言
👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:优选算法
🔑本章内容:链表
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~
一、链表常用技巧和操作总结:
1.1 技巧
- 画图(最重要的形象直观不易出错)
- 引入虚拟"头"节点 ListNode* newhead=new ListNode(0)
- 快慢双指针
1.2 常用操作
- 创建一个新的节点(new)
- 尾插
- 头插(逆序)
二、链表示例:
2.1 两数相加
- 题⽬链接:2. 两数相加
- 题⽬描述:
- 解法(模拟):
算法思路:
两个链表都是逆序存储数字的,即两个链表的个位数、⼗位数等都已经对应,可以直接相加。在相加过程中,我们要注意是否产⽣进位,产⽣进位时需要将进位和链表数字⼀同相加。如果产⽣进位的位置在链表尾部,即答案位数⽐原链表位数⻓⼀位,还需要再 new ⼀个结点储存最⾼位。 - C++ 代码
/**
* 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 tmp=0;
while(cur1||cur2||tmp)
{
if(cur1)
{
tmp+=cur1->val;
cur1=cur1->next;
}
if(cur2)
{
tmp+=cur2->val;
cur2=cur2->next;
}
prev->next=new ListNode(tmp%10);
prev=prev->next;
tmp/=10;
}
prev=newhead->next;
delete newhead;
return prev;
}
};
2.2 两两交换链表中的节点
- 题⽬链接:24. 两两交换链表中的节点
- 题⽬描述:
- 解法(模拟):
算法思路:
画图画图画图,重要的事情说三遍~ - C++代码
/**
* 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,*dest=cur->next;
while(cur&&dest)
{
cur->next=dest->next;
dest->next=cur;
prev->next=dest;
prev=cur;
cur=cur->next;
if(cur!=nullptr)
dest=cur->next;
}
return newhead->next;
}
};
2.3 重排链表
- 题⽬链接:143. 重排链表
- 题⽬描述:
- 解法:
算法思路:
画图画图画图,重要的事情说三遍~
- 找中间节点;
- 中间部分往后的逆序;
- 合并两个链表
- C++代码
/**
* 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*slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
//对中间节点之后的节点进行逆序---分割成两个链表+头插
ListNode* cur=slow->next;
slow->next=nullptr;// 注意把两个链表给断开
ListNode* newhead=new ListNode(0);
ListNode* prev=nullptr;
while(cur)
{
ListNode* next=cur->next;
cur->next=prev;
newhead->next=cur;
prev=cur;
cur=next;
}
//合并两个链表
ListNode* cur1=head,*cur2=newhead->next;
ListNode* ret=new ListNode(0);
ListNode* cur3=ret;
while(cur1)
{
cur3->next=cur1;
cur1=cur1->next;
cur3=cur3->next;
if(cur2)
{
cur3->next=cur2;
cur2=cur2->next;
cur3=cur3->next;
}
}
head=ret->next;
delete newhead;
delete ret;
}
};
2.4 合并 K 个升序链表
-
题⽬链接:23. 合并 K 个升序链表
-
题⽬描述:
-
解法⼀(利⽤堆):
算法思路:
合并两个有序链表是⽐较简单且做过的,就是⽤双指针依次⽐较链表 1 、链表 2 未排序的最⼩元素,选择更⼩的那⼀个加⼊有序的答案链表中。
合并 K 个升序链表时,我们依旧可以选择 K 个链表中,头结点值最⼩的那⼀个。那么如何快速的得到头结点最⼩的是哪⼀个呢?⽤堆这个数据结构就好啦~
我们可以把所有的头结点放进⼀个⼩根堆中,这样就能快速的找到每次 K 个链表中,最⼩的元素是哪个。 -
解法⼆(递归/分治):
算法思路:
逐⼀⽐较时,答案链表越来越⻓,每个跟它合并的⼩链表的元素都需要⽐较很多次才可以成功排序。
⽐如,我们有 8 个链表,每个链表⻓为 100。 逐⼀合并时,我们合并链表的⻓度分别为(0, 100), (100, 100), (200, 100), (300, 100), (400, 100), (500, 100), (600, 100), (700, 100)。所有链表的总⻓度共计 3600。
如果尽可能让⻓度相同的链表进⾏两两合并呢?这时合并链表的⻓度分别是(100, 100) x 4, (200, 200) x 2, (400, 400),共计 2400。⽐上⼀种的计算量整整少了 1/3。
迭代的做法代码细节会稍多⼀些,这⾥给出递归的实现,代码相对简洁,不易写错。
算法流程:
- 特判,如果题⽬给出空链表,⽆需合并,直接返回;
- 返回递归结果。
递归函数设计:
- 递归出⼝:如果当前要合并的链表编号范围左右值相等,⽆需合并,直接返回当前链表;
- 应⽤⼆分思想,等额划分左右两段需要合并的链表,使这两段合并后的⻓度尽可能相等;
- 对左右两段分别递归,合并[l, r]范围内的链表;
- 再调⽤ mergeTwoLists 函数进⾏合并(就是合并两个有序链表)
- C++代码
/**
* 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:
struct cmp
{
bool operator()(const ListNode* l1,const ListNode* l2)
{
return l1->val>l2->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists)
{
ListNode* newhead=new ListNode(0);
priority_queue<ListNode*,vector<ListNode*>,cmp> pq;
for(auto&e:lists)
if(e) pq.push(e);//这里要判断一下每个分组里面节点是否为空
ListNode* cur=newhead;
while(!pq.empty())
{
ListNode* tmp=pq.top();
pq.pop();
cur->next=tmp;
cur=tmp;
if(tmp->next)
pq.push(tmp->next);
}
ListNode* ret=newhead->next;
delete newhead;
return ret;
}
};
-------------------------------------------------------------------------------------------
/**
* 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* mergeSort(vector<ListNode*>&lists,int left,int right)
{
//递归出口
if(left>right)return nullptr;
if(left==right)return lists[left];
//平分数组
int mid=left+((right-left)>>1);
//处理左右区间
ListNode* l1=mergeSort(lists,left,mid);
ListNode* l2=mergeSort(lists,mid+1,right);
//合并两个有序链表
return mergeTwoList(l1,l2);
}
ListNode* mergeTwoList(ListNode* l1,ListNode* l2)
{
if(l1==nullptr)return l2;
if(l2==nullptr)return l1;
ListNode head;
ListNode*cur=&head;
while(l1&&l2)
{
if(l1->val<=l2->val)
{
cur->next=l1;
l1=l1->next;
cur=cur->next;
}
else
{
cur->next=l2;
l2=l2->next;
cur=cur->next;
}
}
if(l1)cur->next=l1;
if(l2)cur->next=l2;
return head.next;
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
return mergeSort(lists,0,lists.size()-1);
}
};
2.5 K 个⼀组翻转链表
- 题⽬链接:25. K 个⼀组翻转链表
- 题⽬描述:
- 解法(模拟):
算法思路:
本题的⽬标⾮常清晰易懂,不涉及复杂的算法,只是实现过程中需要考虑的细节⽐较多。
我们可以把链表按 K 个为⼀组进⾏分组,组内进⾏反转,并且记录反转后的头尾结点,使其可以和前、后连接起来。思路⽐较简单,但是实现起来是⽐较复杂的。
我们可以先求出⼀共需要逆序多少组(假设逆序 n 组),然后重复 n 次⻓度为 k 的链表的逆序即可 - C++代码
/**
* 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)
{
ListNode* cur=head;
int cnt=0;
while(cur)
{
cur=cur->next;
cnt++;
}
int ret=cnt/k;
ListNode* newhead=new ListNode(0);
ListNode* prev=newhead,*cur1=head;
for(int i=0;i<ret;i++)
{
ListNode* tmp=cur1;
for(int j=0;j<k;j++)
{
ListNode* cur2=cur1->next;
cur1->next=prev->next;
prev->next=cur1;
cur1=cur2;
}
prev=tmp;
}
prev->next=cur1;
ListNode* dest=newhead->next;
delete newhead;
return dest;
}
};