LeetCode | Merge k Sorted Lists(归并k个链表)

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

将k个链表排序,并求出时间复杂度。


方案一:

第一个和第二个合并,结果在和第三个合并……最后和第k个合并。本来以为时间复杂度为nk,其实不然。前两个合并后,耗费2n个时间。再和第三个合并,耗费3n个时间,最后和第k个合并,耗费kn个时间。总的时间为(2+3…+k)*n = n*k*k的时间复杂度。所有不可取。这种好实现,循环n次,然后两个两个合并即可。


方案二:

其实可以把k个链表看成k个点,对一个含有k个元素的数组,我们有快速排序、归并排序、堆排序等。但由于这道题目的应用环境,每个元素含有n个数据,快速排序不可能实现。那么考虑归并排序。其实在方案一当中,两两之间的合并已经是归并排序了。那么现在对整个k个链表应用归并排序。每次归并排序,nk个元素都要比较,时间复杂度为nk,一共需要logk次归并,所以时间复杂度为O(nklogk)。

有两种实现方法,递归和循环。其中循环的方法值得借鉴

//通用方法,这里不管有多少个链表
ListNode *MergeKLists(LinkList arr[],int l,int h)
{
    if(l < r){
        int m = (l+r)/2;
        return Merge(MergeKLists(arr,l,m),MergeKLists(arr,m+1,h));
    }
    return arr[l];
}

//当能求得链表的个数的时候,可以利用循环代替递归来进行
ListNode *MergeKLists(LinkList arr[],int k)
{
    LinkList first,second,result;

    if(k < 1)
        return NULL;
    if(k == 1)
        return arr[0];

    while(k > 1){
        int half = (k+1)/2; //这里必须加1,因为k代表的是个数,当循环结束后,会有k = half,当k为基数的时候,个数会减少
        for(int i = 0;i + half < size;i++){
            first = arr[i];
            second = arr[i+half];
            result = Merge(first,second);
            arr[i] = result;
        }
        k = half;
    }
    return arr[0];
}


ListNode *Merge(LinkList l1,LinkList l2)
{
    ListNode *dummy = (LinkList)malloc(sizeof(ListNode));
    dummy->next = l1;   //初始化,为后面求解方便
    ListNode *cur = dummy;  //父节点
    while(l1 != NULL && l2 != NULL){
        if(l1->data < l2->data)
            l1 = l1->next;
        else{
            LinkList temp = l2->next;
            l2->next = cur->next;
            cur->next = l2;
            l2 = temp;
        }
        cur = cur->next;
    }
    if(l2 != NULL)
        cur->next = l2;

    return dummy->next;
}

假设总共有k个list,每个list的最大长度是n,那么运行时间满足递推式T(k) = 2T(k/2)+O(n*k)。根据主定理,可以算出算法的总复杂度是O(nklogk)。如果不了解主定理的朋友,可以参见 主定理-维基百科 。空间复杂度的话是递归栈的大小O(logk)。 

方案三:

我们还要尝试堆排序。我们可以先将k个链表的第一个元素进行堆排序,那么取出最小数据,并将最小数据所在链表的下一个元素放入堆中。因此一次取元素,时间复杂度为logk,一共有nk个元素。总的时间复杂度为O(nklogk)。空间复杂度是堆的大小,即为O(k)。

由于以往看到的堆排序都是在一个数组中进行,比较好写,但是这个题目的元素是一个结点,并且也不在数组中。所以可能的堆排序结点为(没有具体实现,只是考虑了一下):

typedef struct Node{
    int data;
    struct Node *next;
}ListNode,*LinkList;

typedef struct HeapNode{
    ListNode *LNode;
    struct HeapNode *left,*right;
}HeapNode;

但是在C++中好实现,已经封装好了堆的实现。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class ListNodeCompare:public binary_function<ListNode*,ListNode*,bool>
{
public:
    bool operator()(ListNode* t1,ListNode* t2)const
    {
        if ( !t1||!t2 )
            return !t2;
        return t1->val>t2->val;
    }
};
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        // Note: The Solution object is instantiated only once and is reused by each test case.
        if (lists.empty())
            return NULL;
        priority_queue<ListNode*,vector<ListNode*>,ListNodeCompare> Q;
        for(int i=0;i<lists.size();i++)
            if ( lists[i]!=NULL)
                Q.push(lists[i]);
        ListNode guard(-1);
        ListNode* tail=&guard;
        while(!Q.empty())
        {
            ListNode* toAdd=Q.top();
            Q.pop();
            tail->next=toAdd;
            tail=tail->next;
            if (toAdd->next)
                Q.push(toAdd->next);
        }
        return guard.next;
    }
};


推荐链接:PriorityQueue

点赞