题目链接
题目描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例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 <= 104
- 0 <= lists[i].length <= 500
- -104 <= lists[i][j] <= 104
- lists[i] 按 升序 排列
- lists[i].length 的总和不超过 10^4
算法
与合并两个有序序列的思路一致。
从以上思路出发,考虑合并两个有序链表时候的算法:每次找到两个链表中当前较小的一个,将其合并到结果中。则合并k个有序链表时候,每次从k个链表节点中找到值最小的一个,将其合并到答案中。
要维护多个元素的最小值,则可以自然考虑到堆。
C++中的堆是priority_queue
,要使用小根堆需要在定义的时候指定排序,同时要优先队列维护链表节点,需要自己重新写排序方法。
在题解中使用了类中重载operator()
的方法来自己手写排序方法。
实现中的细节
除了第一个节点,其他的节点应当是让当前节点的next指向下一个找到的节点。则需要对第一个节点特殊处理,如果使用虚拟头结点,则可以避免特殊处理头结点的情况。即new
一个dummy节点作为虚拟头结点,使用一个指针指向虚拟头结点,则所有节点的操作都变成了找到值最小的节点,插入到当前节点的后面。
从上面的示例3可以看到,链表向量中有空链表,这种则不应当加入到堆中。
复杂度分析
时间复杂度:
链表有
k
k
k个,则堆中的元素最大有
k
k
k个,对于堆的插入和删除操作,复杂度是
O
(
l
o
g
k
)
O(log k)
O(logk)。
链表长度为
n
n
n,则所有链表的元素的个数总和最多有
k
×
n
k \times n
k×n个,对于每个点都需要插入和删除一次。
综上,时间复杂度为
O
(
k
×
n
×
l
o
g
k
)
O(k \times n \times log k)
O(k×n×logk)
空间复杂度:
堆中的元素最多等于链表的个数
k
k
k,其他的变量都是常数个。
即空间复杂度为
O
(
k
)
O(k)
O(k)
C++代码
class Solution {
public:
class listGreater
{
public:
bool operator()(const ListNode* p1, const ListNode* p2)
{
return p1->val > p2->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*, vector<ListNode*>, listGreater> q;
for(auto l: lists)
{
if(l)
q.push(l);
}
ListNode* dummy = new ListNode();
ListNode* ptr = dummy;
while(!q.empty())
{
ListNode* t = q.top();
q.pop();
ptr->next = t;
ptr = ptr->next;
if(t->next) q.push(t->next);
}
ptr = dummy->next;
delete dummy;
return ptr;
}
};