leetcode23. Merge k Sorted Lists總結

    哈哈哈哈, 今天第一次解出了leetcode中的困難題型, 心裏有點小激動(>_<), 證明我刷了一個月的leetcode還是有進步滴, 標誌着我向一名合格的程序猿邁出了重要的一步。
    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
    這道題要求將k條有序單鏈表合併爲一條鏈表。 咋一看, 這道題很難, 根本無從下手, 我剛開始也是懵比了好一會。 合併兩條鏈表很容易, 只需要不斷迭代兩條鏈表, 使結果鏈表指向某個值較小的結點即可, 跟歸併排序中的merge函數差不多。 而合併k條鏈表就不容易了, 因爲下一個最小結點不好找, 如果是每次利用遍歷法尋找k個結點中的最小結點, 然後不斷將k條鏈表都迭代到最後的話, 這時候的時間複雜度爲O(n2)。 通常情況下O(n2)的複雜度都可以優化爲O(nlgn)甚至O(n)。 能不能在尋找k個結點中的最小結點上下功夫呢? 當然能! 這時候就引出了第一種做法:
  1. 利用最小堆求解
    堆是神馬東東, 最小堆又是神馬東東??

        堆就是類似於二叉樹的一種數據結構, 但是跟搜索二叉樹不同的是, 它的根節點是所有結點中的最大或者最小的結點, 如果根節點是最大的結點, 這個堆就是最大堆; 如果根結點是最小的結點, 這個堆就是最小堆。 大概就是長這個樣子:
    

    最小堆
    該圖演示了最小堆元素上浮的過程, 將較小元素向上調整到合適的位置; 另外還有一個是下沉, 它將較大元素向下調整位置。

        Java類庫中有一個稱爲PriorityQueue的類, 構造函數中可以以堆的大小爲第一個參數, 以Comparator爲第二個參數, 它規定了元素的比較方式, 可以以此來確定該堆爲最小堆或最大堆。
        尋找最小元素的複雜度爲O(1), 插入元素爲O(lgn),  因此總體合併的複雜度爲O(nlgn)
    
public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(lists.length, new Comparator<ListNode>() {
            public int compare(ListNode l1, ListNode l2) {
                if (l1.val > l2.val)
                    return 1;
                else if (l1.val < l2.val)
                    return -1;
                else
                    return 0;
            }
        });
        for (ListNode list : lists)
            if (list != null)
                queue.add(list);
        ListNode l = new ListNode(0), p = l;
        while (! queue.isEmpty()) {
            p.next = queue.poll();
            p = p.next;

            if (p.next != null)
                queue.add(p.next);
        }
        return l.next;
    }
  1. 利用歸併排序的思路求解

        這個解法跟歸併排序的解法一毛一樣, 就是利用Divide and Conquer的思想將k條鏈表的排序分解爲k/2條鏈表以及另外k/2條鏈表, k/2條鏈表分爲兩組k/4條鏈表...然後依次將它們合併起來。 跟合併排序的複雜度一樣, 該算法的複雜度同樣爲O(nlgn)。
    
public ListNode mergeKLists(ListNode[] lists) {
        return divide(lists, 0, lists.length - 1);

    }
    private ListNode divide(ListNode[] lists, int low, int high) {
        if (low == high)
            return lists[low];
        else if (low < high) {
            int mid = (low + high) / 2;
            ListNode front = divide(lists, low, mid);;
            ListNode back = divide(lists, mid + 1, high);
            return merge(front, back);
        }
        else
            return null;
    } 
    private ListNode merge(ListNode front, ListNode back) {
        if (front == null) return back;
        if (back == null) return front;

        if (front.val < back.val) {
            front.next = merge(front.next, back);
            return front;
        }
        else {
            back.next = merge(front, back.next);
            return back;
        }

    }
    總結: Divide and Conquer的思想在很多數組, 鏈表題總都能適用, 當遇到合併類的問題時, 就該考慮該問題能否先分解爲有限個子問題, 將子問題求解之後再合併爲最終的解, 通常該解法都能得到最大的優化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章