1. 程式人生 > 實用技巧 >23.合併k個有序連結串列

23.合併k個有序連結串列

合併 k 個排序連結串列,返回合併後的排序連結串列。請分析和描述演算法的複雜度。

示例:

輸入:
[
  1->4->5,
  1->3->4,
  2->6
]
輸出: 1->1->2->3->4->4->5->6

我們熟悉的是兩個連結串列之間的合併,這道題的難點在於k值是不固定的,並不知道要合併多少連結串列。一種很常見的想法就是使用分治法,因為合併兩個有序連結串列是合併k個有序連結串列的子問題。

分治法一

多次進行兩個連結串列之間的合併,將k個有序連結串列轉化為(k+1)/2個有序連結串列。例如有6個連結串列時,想要合併為3個連結串列,則連結串列0與連結串列3合併得到新的連結串列0,連結串列1與連結串列4合併得到新的連結串列1,連結串列2與連結串列5合併得到新的連結串列2。最終所有連結串列結點都合併到了連結串列0,即為所求結果。
舉例說明該過程:
[[1,4,5],[1,3,4],[2,6]]->[[1,2,4,5,6],[1,3,4]]->[[1,1,2,3,4,4,5,6]]

    ListNode*mergeTwoLists(ListNode*l1,ListNode*l2){
        ListNode*dummy=new ListNode(),*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;
        }
        if(l1) cur->next=l1;
        if(l2) cur->next=l2;
        return dummy->next;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n=lists.size();
        //n=0返回空,n=1返回lists[0]
        if(n==0) return NULL;
        while(n>1){
            //k為合併的步長
            int k=(n+1)/2;
            //如果n為奇數則正中間連結串列不參與合併
            for(int i=0;i<n/2;++i){
                //將lists[i]和lists[i+k]合併到lists[i]
                lists[i]=mergeTwoLists(lists[i],lists[i+k]);
            }
            //將n個連結串列經過兩兩合併之後轉為(n+1)/2個連結串列
            n=k;
        }
        //n=1退出迴圈,此時所有連結串列都合併進了lists[0]
        return lists[0];
    }

分治法二

類似歸併排序

    ListNode*mergeTwoLists(ListNode*l1,ListNode*l2){
        ListNode*dummy=new ListNode(),*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;
        }
        if(l1) cur->next=l1;
        if(l2) cur->next=l2;
        return dummy->next;
    }
    ListNode*helper(vector<ListNode*>&lists,int left,int right){
        if(left==right) return lists[left];
        int mid=(left+right)/2;
        ListNode*l1=helper(lists,left,mid);
        ListNode*l2=helper(lists,mid+1,right);
        //當前遞迴層的返回值
        return mergeTwoLists(l1,l2);
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n=lists.size();
        if(n==0) return NULL;
        return helper(lists,0,n-1);
    }

最小堆

首先將k個連結串列的首元素新增到最小堆,取出最小元素加入到結果連結串列中。再將所加元素的下一個元素加入到最小堆(此時的最小元素要麼還在最小堆中,要麼是之前所加元素的下一個元素),取出最小元素加入到結果連結串列中。以此類推,直到堆中沒有元素為止。

    public ListNode mergeKLists(ListNode[] lists) {
        int n=lists.length;
        PriorityQueue<ListNode> pq=new PriorityQueue<ListNode>(new Comparator<ListNode>(){
            public int compare(ListNode l1,ListNode l2){
                return l1.val-l2.val;
            }
        });
        ListNode dummy=new ListNode(-1);
        ListNode cur=dummy;
        for(int i=0;i<n;++i) {
            if(lists[i]!=null){ 
            pq.add(lists[i]);
            lists[i]=lists[i].next;
            }
        }
        while(!pq.isEmpty()){
            cur.next=pq.poll();
            cur=cur.next;
            //將取出元素的下一個元素加入堆中
            if(cur.next!=null) pq.add(cur.next);
        }
        return dummy.next;
    }

leetcode原題:https://leetcode-cn.com/problems/merge-k-sorted-lists