https://leetcode.com/problems/merge-k-sorted-lists/?tab=Description
思路1:暴力合并
一个简单的做法是遍历这k个链表,不断调用merge two lists的方法依次合并。这种做法效率很低。
Time complexity: 假设所有的链表长度都是n,那么时间复杂度是O(n + 2n + ... + kn) = O(k^2 n), 其中k是链表个数。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode *ret = NULL;
for (int i = 0; i < lists.size(); ++i) {
ret = merge_two_lists(ret, lists[i]);
}
return ret;
}
ListNode* merge_two_lists(ListNode* l1, ListNode* l2) {
ListNode dummy(0), *cur = &dummy;
while (l1 && l2) {
if (l1->val < l2->val) { cur->next = l1; l1 = l1->next; }
else { cur->next = l2; l2 = l2->next; }
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy.next;
}
};
思路2:分治法
把vector分成左、右两部分,然后分别对左、右两边做mergeKLists,最后合并左、右的有序链表。和归并排序如出一辙。
Time complexity: O(N log k)。其中k是链表个数,N是合并完k个链表后的总长度。
Space complexity: O(1)
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.size() == 0) return NULL;
return helper(lists, 0, lists.size() - 1);
}
private:
ListNode* helper(vector<ListNode*>& lists, int start, int end) {
if (start == end) return lists[start];
int mid = start + (end - start) / 2;
ListNode *left = helper(lists, start, mid);
ListNode *right = helper(lists, mid + 1, end);
return merge_two_lists(left, right);
}
ListNode* merge_two_lists(ListNode* l1, ListNode* l2) {
ListNode dummy(0), *cur = &dummy;
while (l1 && l2) {
if (l1->val < l2->val) { cur->next = l1; l1 = l1->next; }
else { cur->next = l2; l2 = l2->next; }
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy.next;
}
};
思路3:优先队列
- 创建一个优先队列(或最小堆),遍历所有链表,把链表节点推入优先队列;
- 不断从优先队列中弹出所有节点,同时将它们按弹出的先后顺序连接起来,直到队列空,最后得到的结果就是合并后的有序链表。
Time complexity: O(N log N)
Space complexity: O(N). 其中N是所有链表节点总数
C++
struct Cmp {
bool operator()(const ListNode* a, const ListNode* b) const {
return a->val > b->val;
}
};
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int n = lists.size();
if (n == 0) return NULL;
if (n == 1) return lists[0];
priority_queue<ListNode*, vector<ListNode*>, Cmp> pq;
for (int i = 0; i < n; ++i) {
ListNode *head = lists[i];
while (head) {
pq.push(head);
head = head->next;
}
}
ListNode dummy(0), *cur = &dummy;
while (!pq.empty()) {
cur->next = pq.top(); pq.pop();
cur = cur->next;
}
cur->next = NULL; // very important!
return dummy.next;
}
};
在上面的代码中,注意倒数第二句cur->next = NULL
千万不能漏,因为最后弹出的节点不一定就是某个链表的尾节点,不加上这句的话,就会导致得到的链表有环。
思路4:两两合并
记下链表的总数,设为n
- 合并lists[0]和lists[1]并覆盖lists[0],合并lists[2]和lists[3]并覆盖lists[2],...注意如果n是偶数,正好可以全部两两合并完,即最后合并的是lists[n-2]和lists[n-1](合并完覆盖lists[n-2 / 2]);而如果n是奇数,lists[n-1]会“落单”,这时处理方法是直接将lists[n-1]去覆盖lists[n-1 / 2]。
- 更新n:
n = ceil(n / 2.0)
为什么是向上取整?举个例子就清楚了:比如n一开始等于4,第一轮合并后应该变为2个,而如果n一开始等于5,第一轮合并后最后一个落单,所以变为3个。 - 重复步骤1、2,直到n为1
- 返回lists[0]
时间和空间复杂度同分治法。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int n = lists.size();
if (n == 0) return NULL;
if (n == 1) return lists[0];
while (n > 1) {
for (int i = 0; i < n; i += 2) {
if (i != n - 1) {
lists[i / 2] = merge_two_lists(lists[i], lists[i+1]);
} else {
lists[i / 2] = lists[i];
}
}
n = ceil(n / 2.0);
}
return lists[0];
}
private:
ListNode* merge_two_lists(ListNode* l1, ListNode* l2) {
ListNode dummy(0), *cur = &dummy;
while (l1 && l2) {
if (l1->val < l2->val) { cur->next = l1; l1 = l1->next; }
else { cur->next = l2; l2 = l2->next; }
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy.next;
}
};