[LeetCode] Merge k Sorted Lists 解题报告

来源:互联网 发布:win10系统占用80端口 编辑:程序博客网 时间:2024/05/06 16:56

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

[中文翻译]
合并k个已排好序的链表,并将其作为一个链表返回。 分析和描述其复杂性。

[解题思路]
这题的朴素思路可以先参考[LeetCode] Merge Two Sorted Lists 解题报告 。
之前merge的时候是找两个链表头的最小值,比较一下就很容易得到。现在是找k个链表头的最小值,遍历一遍,记下最小值在哪个链表,然后把那个链表头插入结果链表即可。一轮一轮地把最小的那个节点取出来,直到所有链表为空。

这种方法很朴素,假设每个链表都有d个元素,那么效率是O(k2d),因为一共有kd个节点,处理每个节点时都需要用k的时间复杂度选出最小的那个。

我们可以用最小堆的结构来更快地获得最小的节点,效率为O(logk),因此总的效率为O(kdlogk)。堆是一种数据结构,对于N个元素的堆,建堆、插入、删除、修改的时间效率均为O(logN),堆不支持查找。

堆一般为二叉堆,结构如图1。最小堆保证,父节点一定比它的子节点小。

堆的结构
图1 堆的结构

  • 插入
    例如我们需要插入元素34,先将它放在最后的位置,然后不断地与那它与父节点比较,如果比父节点小,就与父节点交换,直到无法继续向上交换。这样就能够在O(logN)的时间内插入一个元素,同时维护最小堆的性质。

    插入
    图2 插入元素

  • 删除
    例如我们需要删除元素2(由于最小堆的目的本身就是连续地获得最小值,所以一般我们都是删除堆顶元素)。我们采取的方法不是把下面的节点向上交换,而是用末尾的节点替换堆顶节点,然后把它不断往下沉。不断的与它子节点中最小的一个交换,直到无法继续下沉。

    删除
    图3 删除元素

  • 修改
    修改和删除一个道理,不断地拿当前元素往下沉。

  • 建堆
    建堆有两种方法,最直接的方法就是在一个空堆中,不断的插入元素,效率为O(NlogN)。另一种方法是,先把乱序的元素放进堆里,然后去维护堆的性质,从后往前遍历节点,把当前节点当做刚修改过的元素不断地下沉,可以证明这种方法的效率为O(N)。

在这个问题里,堆的元素是链表。每次取出堆顶链表的表头,插入结果链表,然后更新堆顶链表,并维护堆。代码实现地比较烦,主要是因为维护的这个堆是一个链表,而不是数字,因此关于一个节点有没有子节点、有多少子节点,以及链表为NULL时,都需要做一些判断,不能像数字一样设一个MaxValue了事。

[C++代码]
Solution1,朴素地找最小值

class Solution {public:    ListNode* mergeKLists(vector<ListNode*>& lists) {        ListNode dummy(0);        ListNode* p = &dummy;        int listNumber = lists.size();        int minIndex, minValue;        while (true) {            minIndex = listNumber;            for (int i = 0; i < listNumber; i++) {                if (lists.at(i) && (minIndex == listNumber || lists.at(i)->val < minValue)) {                    minIndex = i;                    minValue = lists.at(i)->val;                }            }            if (minIndex == listNumber)                break;            p->next = lists.at(minIndex);            lists.at(minIndex) = lists.at(minIndex)->next;            p = p->next;        }        return dummy.next;    }};

Solution2,用堆维护获得最小值

class Solution {public:    ListNode* mergeKLists(vector<ListNode*>& lists) {        ListNode dummy(0);        ListNode* p = &dummy;        ListNode* tmp;        int listsNumber = lists.size();        if (0 == listsNumber)            return NULL;        // 建堆,倒着遍历,可证明效率为O(n)        for (int j = listsNumber - 1; j >= 0; j--) {            int i = j;            while (true) {                int leftSon = 2 * i + 1;                int rightSon = 2 * i + 2;                int nextI = i;                if (leftSon < listsNumber && lists.at(leftSon) && (!lists.at(nextI) || lists.at(leftSon)->val < lists.at(nextI)->val))                    nextI = leftSon;                if (rightSon < listsNumber && lists.at(rightSon) && (!lists.at(nextI) || lists.at(rightSon)->val < lists.at(nextI)->val))                    nextI = rightSon;                if (nextI == i)                    break;                tmp = lists.at(nextI);                lists.at(nextI) = lists.at(i);                lists.at(i) = tmp;                i = nextI;            }        }        while (lists.at(0)) {            p->next = lists.at(0);            p = p->next;            lists.at(0) = lists.at(0)->next;            // 维护堆            int i = 0;            while (true) {                int leftSon = 2 * i + 1;                int rightSon = 2 * i + 2;                int nextI = i;                if (leftSon < listsNumber && lists.at(leftSon) && (!lists.at(nextI) || lists.at(leftSon)->val < lists.at(nextI)->val))                    nextI = leftSon;                if (rightSon < listsNumber && lists.at(rightSon) && (!lists.at(nextI) || lists.at(rightSon)->val < lists.at(nextI)->val))                    nextI = rightSon;                if (nextI == i)                    break;                tmp = lists.at(nextI);                lists.at(nextI) = lists.at(i);                lists.at(i) = tmp;                i = nextI;            }        }        return dummy.next;    }};
0 0