【小白爬Leetcode23】1.9 合并K个排序链表Merge k Sorted Lists
Leetcode 23 h a r d \color{#FF0000}{hard} hard
点击进入原题链接:LeetCode中国站
这道题的前驱:【小白爬Leetcode21】1.8 合并两个有序链表 Merge Two Sorted Lists
题目
Description
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6
中文描述
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
思路一 时间复杂度O(kn2),空间复杂度O(1)的递归法
这道题比合并两个有序链表要难一些,因为合并两个有序链表是一个非此即彼的逻辑关系,如果不插入l1中的节点,一定要插入l2中的节点,直到其中一个链表耗尽(指针到达了NULL),而合并k个有序数组,则需要每次在k个节点中选出最小的那个插入。
采用递归的思路,每层递归都遍历一下现在的Lists,找出其中 val 值最小的node,把这个node插入到链表中,至于这个递归如何实现插入链表,可以参考我的另一篇博文的方法三:
【小白爬Leetcode21】1.8 合并两个有序链表 Merge Two Sorted Lists
完整代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists)
{
//找出K个里val最小的那个节点
int small_index = -1;
int small_val;
int k = lists.size();
for(int i=0;i<k;i++)
{
if(lists[i]){small_index=i;small_val=lists[i]->val;break;}
}
if(small_index==-1){return NULL;} //如果整个lists都是空的,那么返回NULL;
for(int i=small_index+1;i<k;i++) //从small_index+1开始寻找链表中更小的值
{
if(!lists[i]){continue;} //如果剩下的是空链表,直接跳过
if (lists[i]->val < small_val)
{
small_val = lists[i]->val;
small_index = i;
}
}
ListNode* ptr = lists[small_index];
lists[small_index] = ptr->next;
ptr->next = mergeKLists(lists);
return ptr;
}
};
复杂度分析
这种方法,对一共kn个节点,处理每个节点都要遍历一次list(n次),因此时间复杂度为O(n2)
空间复杂度由于只使用几个指针和int型,为 O(1)。
由于是幂次的时间复杂度,当n很大时,耗时陡然上升,最终提交后耗时达到了惊人的808ms(多么吉祥的数字我骄傲了吗?)。
当
然
了
,
本
菜
鸡
只
会
这
一
种
方
法
\color{#FF0000}{当然了,本菜鸡只会这一种方法}
当然了,本菜鸡只会这一种方法
思路二 时间复杂度O(kn*log(kn)),空间复杂度为O(n) 的vector排序法
把每个节点放入vector容器中,对这个容器内的节点按照val值排序,再按照vector的顺序依次连接节点即可。其实有点投机取巧的感觉了。
完整代码如下:
#include<algorithm>
class Solution {
public:
static bool mySort(ListNode* p1, ListNode* p2) //定义的排序仿函数
{
return p1->val < p2->val;
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
std::vector<ListNode*> v;
int length = lists.size();
ListNode* ptr;
for(int i=0;i<length;i++)
{
ptr = lists[i];
while(ptr)
{
v.push_back(ptr);
ptr = ptr->next;
}
}
if(v.empty()){return NULL;} //处理输入为[] 或者 [[]]的情况
std::sort(v.begin(),v.end(),mySort);
// v.sort(mySort); //对v进行排序
length = v.size();
ptr = v[0];
for(int i=0;i<length-1;i++)
{
v[i]->next = v[i+1];
}
v[length-1]->next = NULL;
return ptr;
}
};
小心得
在写这段代码的过程中,我遇到了几个问题,基本都是把python的习惯带到了C++中:
- 很基础的问题:for(int i)这种结构中,i的生命周期 == for循环的生命周期,for循环结束后,i的地址被回收,此时再用 i 就会报use of undeclared xxx 的错;
- 我这里定义了一个函数mySort用来制定我自己的排序规则,这个函数首先不能被定义在
ListNode* mergeKLists(vector<ListNode*>& lists) {}
内部,C++不允许这么做,会抛出一个错误: function definition is not allowed here; - 同样这个mySort函数不能直接
bool mySort(ListNode* p1, ListNode* p2)
,因为 std::sort 的作用域是std,它可不认识你的Solution作用域,所以也不会认识类成员函数mySort,因此要在mySort前面加上static,如下:static bool mySort(ListNode* p1, ListNode* p2)
否则会抛出错误:reference to non-static member function must be called; - 还有就是我对v使用了v.sort(mySort),抛出错误:vector has no member named ''sort"。这种sort方式是List链表的,因为链表不可以随机访问,用不了里的sort,所以给List类定义了一个sort。vector单独定义sort,是因为它可以按照地址随机访问,所以直接用里的sort,格式如下:
std::sort(v.begin(),v.end(),mySort);
。
复杂度分析
我们知道,排序算法的最佳时间复杂度为 nlogn ,这里一共有 kn 个节点,因此排序的时间复杂度为 knlog(kn) ;如果将节点 push_back() 进vector 和 将链表重新 顺次连接 算作 2n 次操作,那么一共需要执行 knlog(kn)+2n 次操作,时间复杂度为 O(knlog(kn)+2n) =
O
(
k
n
∗
l
o
g
(
k
n
)
)
\color{#FF0000}{O(kn*log(kn))}
O(kn∗log(kn)) (忽略低阶无穷大2n)。
至于空间复杂度,由于创造了一个vector,所以为
O
(
k
n
)
\color{#FF0000}{O(kn)}
O(kn)。
思路三 时间复杂度O(kn*logk) 的分治法
B
站
上
看
到
的
解
法
\color{#FF0000}{B站上看到的解法}
B站上看到的解法
点击进入原视频
首先沿用【小白爬Leetcode21】1.8 合并两个有序链表 Merge Two Sorted Lists 中的方法三,写出一个mergeTwoLists方法。
然后写这道题的函数ListNode* mergeKLists(vector<ListNode*>& lists) {}
,这也是个递归函数:
每层递归过程中,都用两个 vector 存放 前半段lists 和 后半段lists ,然后开始两个分支:
ListNode* l1 = mergeKLists(v1);
ListNode* l2 = mergeKLists(v2);
最后再合并这两个分支:
return mergeTwoLists(l1,l2);
大概长这样:
完整代码如下:
class Solution {
public:
ListNode* mergeTwoLists(ListNode*l1,ListNode*l2)
{
if(!l1){return l2;}
if(!l2){return l1;}
if(l1->val<l2->val)
{
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}
else
{
l2->next = mergeTwoLists(l2->next,l1);
return l2;
}
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
int length = lists.size();
if(length == 0){return NULL;} //空vector
if(length == 1){return lists[0];}
if(length == 2){return mergeTwoLists(lists[0],lists[1]);}
vector<ListNode*> v1;
vector<ListNode*> v2;
int mid = length/2;
for(int i=0;i<mid;i++)
{
v1.push_back(lists[i]);
}
ListNode* l1 = mergeKLists(v1);
for(int j=mid;j<length;j++)
{
v2.push_back(lists[j]);
}
ListNode* l2 = mergeKLists(v2);
return mergeTwoLists(l1,l2);
}
};
AC后: