原题地址(困难)

此题虽是困难,但是做法超级多啊,而且也有很容易想到的做法。

方法1—顺序合并

思路

这是我最想想到的做法,很简单。
两个链表合并我们都会,多个链表呢?用个循环呗。就这么简单。

代码

  1. class Solution {
  2. public:
  3. ListNode* mergeKLists(vector<ListNode*>& lists) {
  4. if(lists.size() == 0) return {};
  5. ListNode* dummy = new ListNode(0); //虚头结点
  6. ListNode* p, *q, *pre, *tmp;
  7. dummy->next = lists[0];
  8. for(int i=1; i<lists.size(); i++) {
  9. // 把第i个链表和ret合并在一起
  10. pre = dummy;
  11. p = dummy->next;
  12. q = lists[i];
  13. while(q && p) {
  14. if(q->val <= p->val) {
  15. tmp = q;
  16. q = q->next;
  17. pre->next = tmp;
  18. tmp->next = p;
  19. }
  20. else{
  21. p = p->next;
  22. }
  23. pre = pre->next;
  24. }
  25. if(!p && q) {
  26. pre->next = q;
  27. }
  28. }
  29. return dummy->next;
  30. }
  31. };

时空复杂度

时间复杂度

假设链表最长长度为23. 合并K个升序链表 - 图1,共有23. 合并K个升序链表 - 图2个链表,那么时间复杂度为23. 合并K个升序链表 - 图3
因为有23. 合并K个升序链表 - 图4个链表,第一次合并,是合并第一、第二个链表;第二次合并,实质是第一、二个链表合并后的链表,再和第三个链表合并,因此所耗费时间为23. 合并K个升序链表 - 图5,所以时间复杂度为23. 合并K个升序链表 - 图6

空间复杂度

因为是原地修改,所以空间复杂度为23. 合并K个升序链表 - 图7,如果不是原地修改的话,空间复杂度就为23. 合并K个升序链表 - 图8

方法2—分治合并

思路

可以用分治的思路对方法一进行优化,从而使时间复杂度降低。
盗用官方图:
image.png

代码

  1. class Solution {
  2. public:
  3. ListNode* mergeTwoLists(ListNode* a, ListNode* b) {
  4. if(!a || !b) return a ? a : b;
  5. ListNode *dummy = new ListNode(0);
  6. ListNode *p = a, *q = b, *tail = dummy;
  7. // 尾插法
  8. while(p && q) {
  9. if(p->val <= q->val) {
  10. tail->next = p;
  11. p = p->next;
  12. }
  13. else{
  14. tail->next = q;
  15. q = q->next;
  16. }
  17. tail = tail->next;
  18. }
  19. tail->next = p ? p : q;
  20. return dummy->next;
  21. }
  22. ListNode* merge(vector<ListNode*> lists, int l, int r){
  23. if(l == r) return lists[l];
  24. if(l > r) return nullptr;
  25. int mid = (l + r) / 2;
  26. ListNode* a = merge(lists, l, mid);
  27. ListNode* b = merge(lists, mid+1, r);
  28. return mergeTwoLists(a, b);
  29. }
  30. ListNode* mergeKLists(vector<ListNode*>& lists) {
  31. int n = lists.size();
  32. if(n == 0) return nullptr;
  33. return merge(lists, 0, n - 1);
  34. }
  35. };

时空复杂度

时间复杂度

还是假设链表最长长度为23. 合并K个升序链表 - 图10,共有23. 合并K个升序链表 - 图11个链表。
第一次合并,共合并23. 合并K个升序链表 - 图12次,每次耗费时间2n;
第二次合并,共合并23. 合并K个升序链表 - 图13次,每次耗费时间4n;
第i次合并,共合并 23. 合并K个升序链表 - 图14次,每次耗费时间23. 合并K个升序链表 - 图15;
所以总耗费时间为23. 合并K个升序链表 - 图16,所以时间复杂度为23. 合并K个升序链表 - 图17

空间复杂度

递归使用23. 合并K个升序链表 - 图18

方法3—优先队列

思路

这可能是这道题的最优方法,也是出题人所希望看到的方法。
那就是维护一个优先队列,每次弹出所有链表中最小值的那个节点,再用这个链表的下一个节点补上,直至所有链表都被弹完。

代码

  1. class Solution {
  2. public:
  3. struct Status{
  4. int val;
  5. ListNode* node;
  6. bool operator < (const Status& s) const {
  7. return val > s.val;
  8. }
  9. };
  10. ListNode* mergeKLists(vector<ListNode*>& lists) {
  11. priority_queue<Status> q;
  12. for(auto list : lists) {
  13. if(list) q.push({list->val, list});
  14. }
  15. ListNode head, *tail = &head;
  16. while(!q.empty()) {
  17. auto tmp = q.top();
  18. q.pop();
  19. tail->next = tmp.node;
  20. tail = tail->next;
  21. if(tmp.node->next) q.push({tmp.node->next->val, tmp.node->next});
  22. }
  23. return head.next;
  24. }
  25. };

时空复杂度

时间复杂度

长度为k的优先队列插入删除元素耗费时间都是23. 合并K个升序链表 - 图19,k个链表元素总数量不超过23. 合并K个升序链表 - 图20,所以时间复杂度为23. 合并K个升序链表 - 图21

空间复杂度

23. 合并K个升序链表 - 图22