一、合并两个排序的链表
解法一:迭代(推荐)
- 首先考虑空链表的情况,只要有一为空,答案即为另一链表
- 设置新链表表头,连接两个链表排序后的节点
- 遍历两个链表,取值较小的追加在新链表后,节点指针后移更新
- 剩余节点之间连接即可
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL)//先考虑为空的情况
return pHead2;
if(pHead2==NULL)
return pHead1;
ListNode*dummy=new ListNode(-1),*cur=dummy;//设置表头
while(pHead1&&pHead2)//两链表都不为空
{
if(pHead1->val<=pHead2->val)/取值较小的
{
cur->next=pHead1;//连接
pHead1=pHead1->next;//后移更新节点
}
else
{
cur->next=pHead2;
pHead2=pHead2->next;
}
cur=cur->next;//更新当前指针
}
cur->next=pHead1?pHead1:pHead2;//连接剩余部分
return dummy->next;
}
};
** 时间复杂度:O(n),最坏情况遍历两个链表的所有节点,
空间复杂度:O(1)**
解法二:递归
- 每次比较两链表节点的值,取较小的链表指针向后移动,另一个进入递归
- 将递归的结果连接在较小值节点的后面,等效于在较小值后面添加节点
- 递归终止条件为两链表为空
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL)
return pHead2;
if(pHead2==NULL)
return pHead1;
if(pHead1->val<=pHead2->val)
{
pHead1->next=Merge(pHead1->next,pHead2);//连接递归后的节点,每次更新较小值的指针
return pHead1;//返回表头
}
else
{
pHead2->next=Merge(pHead1,pHead2->next);
return pHead2;
}
}
};
递归思想由小及大,是一个累计的过程,设定一种模式让其重复执行
时间复杂度:O(n),最坏情况遍历两个链表的所有节点,
空间复杂度:O(n),递归栈长度最大为n
解法三:容器
- 设置虚拟节点用于连接节点
- 将节点存入list容器
- 排序取出连接构造新链表(注意自定义排序)
class cmp
{
public:
bool operator()(ListNode*l1,ListNode*l2)
{
return l1->val>l2->val;
}
};
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL)
return pHead2;
if(pHead2==NULL)
return pHead1;
ListNode*dummy=new ListNode(-1),*cur=dummy;
list<ListNode*>l;
while(pHead1)
{
l.push_back(pHead1);
pHead1=pHead1->next;
}
while(pHead2)
{
l.push_back(pHead2);
pHead2=pHead2->next;
}
l.sort(cmp());
int len=l.size();
for(int i=0;i<len;i++)
{
cur->next=l.back();
l.pop_back();
cur=cur->next;
}
cur->next=NULL;
return dummy->next;
}
};
时间复杂度:O(n),遍历两个链表的所有节点,
空间复杂度:O(n),容器占用两链表大小的空间
二、合并K个已排序的链表
解法一:归并排序思想
- 对于k个链表,相当于k个合并数列的子问题,需要两个合并,不断向上合并为一个完整的链表
- 从链表的首、尾开始,每次从中间划分为两半
- 将这两半子问题合并好后就有了两个有序链表,最后将这两链表合并就行了,依据子问题递归处理
- 当左右区间相等或左边大于右边时,终止合并
class Solution {
public:
ListNode*Merge(ListNode*pHead1,ListNode*pHead2)//这即是题目一中的两链表合并
{
if(pHead1==NULL)
return pHead2;
if(pHead2==NULL)
return pHead1;
ListNode*head=new ListNode(-1),*cur=head;
while(pHead1&&pHead2)
{
if(pHead1->val<=pHead2->val)
{
cur->next=pHead1;
pHead1=pHead1->next;
}
else
{
cur->next=pHead2;
pHead2=pHead2->next;
}
cur=cur->next;
}
cur->next=pHead1?pHead1:pHead2;
return head->next;
}
ListNode*divideMerge(vector<ListNode*>&lists,int left,int right)//划分合并区域
{
if(left>right)
return NULL;
if(left==right)//当取到中间一个的情况时返回中间节点
return lists[left];
int mid=(left+right)/2;//从中间分成两段,再合并分好的两段
return Merge(divideMerge(lists, left, mid),divideMerge(lists, mid+1, right));
}
ListNode *mergeKLists(vector<ListNode *> &lists) {
return divideMerge(lists, 0, lists.size()-1);//k个链表归并排序
}
};
时间复杂度:O(n*k),n为所有链表总节点数,最坏每次合并都为O(n),分治为二叉树型递归,每个节点都要使用一次合并,要合并k-1次
空间复杂度:O(
log
2
\log_2
log2k),最坏情况下递归
log
2
\log_2
log2k层,占用
log
2
\log_2
log2k的递归栈
解法二:优先队列
优先可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。
- 首先构造一个比较链表节点大小的小顶堆
- 先遍历k个链表头,将非空节点加入优先队列
- 依次弹出优先队列里的最小元素,将其连接在合并后的链表后面,随后将该节点的原本后继节点(不为空)加入队列,类似上面双指针
class cmp
{
public:
bool operator()(ListNode*l1,ListNode*l2)//设置排序方式,降序
{
return l1->val>l2->val;
}
};
class Solution {
public:
ListNode *mergeKLists(vector<ListNode *> &lists) {
priority_queue<ListNode*,vector<ListNode*>,cmp>pq;//小顶堆
for(int i=0;i<lists.size();i++)
{
if(lists[i]!=NULL)//将所有不为空的链表头加入到小堆中
pq.push(lists[i]);
}
ListNode*dummy=new ListNode(-1),*cur=dummy;//设置虚拟头
while(!pq.empty())//直到小堆为空
{
ListNode*tmp=pq.top();//取出最小元素
pq.pop();
cur->next=tmp;//连接
cur=cur->next;
if(tmp->next!=NULL)//每次再取出链表后一个元素加入节点,进行排序
pq.push(tmp->next);
}
return dummy->next;
}
};
时间复杂度:O(n
log
2
\log_2
log2k),n为链表节点总数,最坏遍历所有节点,每次加入优先队列排序要O(
log
2
\log_2
log2k)
空间复杂度:O(K),优先队列大小不超过k
二再一的基础上设置多个链表,考察对算法和数据结构的使用