算法设计与分析(六

Merge k Sorted Lists

题目

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

Example:

Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6

题目分析

题目很短,需要我们将k个已经有序的数组合并成一个有序数组,这些数组是用链表的方式表示的。很明显,这是归并排序和分治算法结合的题目。

基础知识

  • 归并排序
    归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。一般我们接触的归并排序是二路归并排序,具体的过程如下:
    在这里插入图片描述
    这个过程不断将一个数组分割,分割到两个或者一个元素,然后又将有序的小的数组两两合并成一个有序的数组。这个排序算法是稳定的。

解答及算法步骤

这里产生了两种基本的想法,由于最后要合成一个数组,假设有A,B,C,D,E,F,G,H六个有序的数组

  • 第一种想法
    先合并A和B为A,A和C合并结果为A,A合并D作为A,再合并E为A,再合并F为A,再合并G为A,再合并H为A,最后A即为结果。

    • 算法步骤
      1. 新建一个链表存储答案。
      2. 合并vector的前两个链表,并将合并的链表删除,将结果存储回vector首部。
      3. 继续2的操作,知道vector只有一个元素。
  • 第二种想法
    这可以说是对第一种想法的改进,时间复杂度更低,先合并A和B为A1,再合并C和D为A2,再合并E和F为A3,再合并G和H为A4,再合并A1和A2为B1,再合并A3和A4为B2,最后合并B1和B2。这刚刚好是归并排序的逆过程。

    • 算法步骤
      1. 将vector分成两个部分。
      2. 将这两个部分的链表两两合并。
      3. 递归过2,知道只有一个元素。

源码

  • 做法一
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) {
            return NULL;
        }
        //every two merge
        while(lists.size() > 1) {
        	ListNode *temp = merge(lists[0], lists[1]);
            lists.erase(lists.begin());
        	lists.erase(lists.begin());
        	lists.insert(lists.begin(),temp);
        	
        }
        return lists[0];
    }
    ListNode* merge(ListNode *l1, ListNode *l2) {
    	if(l1 == NULL) {
    		return l2;
    	}
    	if(l2 == NULL) {
    		return l1;
    	}
    	if(l1->val < l2->val) {
    		l1->next = merge(l1->next, l2);
    		return l1;
    	} else {
    		l2->next = merge(l2->next, l1);
    		return l2;
    	}
    }
};
  • 做法二
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //every two merge
        if(lists.empty()) {
            return NULL;
        }
        return merge(lists, 0, lists.size()-1);
    }


    ListNode* merge_two(ListNode *l1, ListNode *l2) {
    	if(l1 == NULL&&l2 == NULL) {
    		return NULL;
    	}
    	if(l1 == NULL) {
    		return l2;
    	}
    	if(l2 == NULL) {
    		return l1;
    	}
    	if(l1->val < l2->val) {
    		l1->next = merge_two(l1->next, l2);
    		return l1;
    	} else {
    		l2->next = merge_two(l2->next, l1);
    		return l2;
    	}
    }

    ListNode* merge(vector<ListNode*>& lists, int begin, int end) {
        //condition to end
        if(begin == end) {
            return lists[begin];
        }

        int middle = (begin + end)/2;

        ListNode *l1 = merge(lists, begin, middle);
        ListNode *l2 = merge(lists, middle+1, end);

        return merge_two(l1, l2);
    }
};

复杂度分析

表面看来这两种做法的时间复杂度是一样的,但我们运行后发现法一解决问题平均时间为216ms,而法二需要20ms,下面我们分别分析一下这两种做法的时间复杂度。

  • 法一
    法一的事件复杂度为O(k2k^2n)
    第一个和第二个需要时间为O(n+n)
    前面合并的2n个元素的与下一个合并时间为O(2n+n)

    最后一步的时间为O((k-1)n+n)

最后所有时间的和为O(k2k^2n)

  • 法二
    法二的时间复杂度为O(nklognk)
    根据大师定理
    有T(k,n)=T([k2\frac{k}{2}],n)+T([k2\frac{k}{2}],n)+O(kn)
    和T(1,n)=O(1)
    可以推出T(k,n)=O(klogkn)

更多技术博客https://vilin.club/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章