LeetCode(23):合并K个排序链表
Hard!
题目描述:
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入: [ 1->4->5, 1->3->4, 2->6 ] 输出: 1->1->2->3->4->4->5->6
解题思路:
这道题让我们合并k个有序链表,之前我们做过一道Merge Two Sorted Lists 混合插入有序链表,是混合插入两个有序链表。这道题增加了难度,变成合并k个有序链表了,但是不管合并几个,基本还是要两两合并。
那么我们首先考虑的方法是能不能利用之前那道题的解法来解答此题。答案是肯定的,但是需要修改,怎么修改呢,最先想到的就是两两合并,就是前两个先合并,合并好了再跟第三个,然后第四个直到第k个。这样的思路是对的,但是效率不高,没法通过OJ,所以我们只能换一种思路。
这里就需要用到分治法 Divide and Conquer Approach。简单来说就是不停的对半划分,比如k个链表先划分为合并两个k/2个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子来说比如合并6个链表,那么按照分治法,我们首先分别合并1和4,2和5,3和6。这样下一次只需合并3个链表,我们再合并1和3,最后和2合并就可以了。
C++解法一:
1 class Solution { 2 public: 3 ListNode *mergeKLists(vector<ListNode *> &lists) { 4 if (lists.size() == 0) return NULL; 5 int n = lists.size(); 6 while (n > 1) { 7 int k = (n + 1) / 2; 8 for (int i = 0; i < n / 2; ++i) { 9 lists[i] = mergeTwoLists(lists[i], lists[i + k]); 10 } 11 n = k; 12 } 13 return lists[0]; 14 } 15 16 ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { 17 ListNode *head = new ListNode(-1); 18 ListNode *cur = head; 19 while (l1 && l2) { 20 if (l1->val < l2->val) { 21 cur->next = l1; 22 l1 = l1->next; 23 } else { 24 cur->next = l2; 25 l2 = l2->next; 26 } 27 cur = cur->next; 28 } 29 if (l1) cur->next = l1; 30 if (l2) cur->next = l2; 31 return head->next; 32 } 33 };
再来看另一种解法,这种解法利用了最小堆这种数据结构,我们首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后我们每次取出最小的那个元素加入我们最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可。
C++解法二:
1 struct cmp { 2 bool operator () (ListNode *a, ListNode *b) { 3 return a->val > b->val; 4 } 5 }; 6 7 class Solution { 8 public: 9 ListNode *mergeKLists(vector<ListNode *> &lists) { 10 priority_queue<ListNode*, vector<ListNode*>, cmp> q; 11 for (int i = 0; i < lists.size(); ++i) { 12 if (lists[i]) q.push(lists[i]); 13 } 14 ListNode *head = NULL, *pre = NULL, *tmp = NULL; 15 while (!q.empty()) { 16 tmp = q.top(); 17 q.pop(); 18 if (!pre) head = tmp; 19 else pre->next = tmp; 20 pre = tmp; 21 if (tmp->next) q.push(tmp->next); 22 } 23 return head; 24 } 25 };
天雨虽宽,不润无根之草。
佛门虽广,不渡无缘之人。