zoukankan      html  css  js  c++  java
  • LeetCode | Merge k Sorted Lists

    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:优先队列

    1. 创建一个优先队列(或最小堆),遍历所有链表,把链表节点推入优先队列;
    2. 不断从优先队列中弹出所有节点,同时将它们按弹出的先后顺序连接起来,直到队列空,最后得到的结果就是合并后的有序链表。

    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

    1. 合并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]。
    2. 更新n: n = ceil(n / 2.0) 为什么是向上取整?举个例子就清楚了:比如n一开始等于4,第一轮合并后应该变为2个,而如果n一开始等于5,第一轮合并后最后一个落单,所以变为3个。
    3. 重复步骤1、2,直到n为1
    4. 返回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;
        }
    };
    
  • 相关阅读:
    MFC的自定义消息(用户消息)的定义与使用
    队列的使用
    接私活平台
    下载图标网站
    C#对象序列化
    C#中控件对比(DataGridView,ListView,TreeView,ComboBox)
    C#反射
    C#可选实参和命名实参
    C#扩展方法
    Lambda表达式
  • 原文地址:https://www.cnblogs.com/ilovezyg/p/6384604.html
Copyright © 2011-2022 走看看