LeetCode-Merge k Sorted Lists

来源:互联网 发布:过山车大亨3 for mac 编辑:程序博客网 时间:2024/05/21 09:39

算法分析与设计,第7周博客

23. Merge k Sorted Lists

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

题意是,给出k个已排好序的链表,把它们合并成一个链表。看完题目以后,可能不是很清楚,但是看到给出的函数接口会更加的明白。所给出的代码如下:

/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */class Solution {    public ListNode mergeKLists(ListNode[] lists) {            }}

给出了k个链表的头节点组成的数组lists,其中的每个元素是每个链表的head节点,这样一来,题目的意思就很清楚了。

首先,考虑最简单的办法,每次取数组里面最小的那个,放在已排序的链表的尾部,然后再把这个节点改成它的下一个节点放在数组里,如此反复直到每个链表都到了尾部。这个方法的代码如下:

/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */class Solution {    ListNode head(ListNode[] cur) {        int k = cur.length;        ListNode res = new ListNode(Integer.MAX_VALUE);        int min = 0;        for (int i = 0; i < k; ++i) {            if (cur[i] != null && (cur[min] == null || cur[i].val < cur[min].val)) {                min = i;            }        }        res = cur[min];        cur[min] = cur[min].next;        return res;    }        boolean over(ListNode[] cur) {        for (int i = 0; i < cur.length; ++i) {            if (cur[i] != null)                return false;        }        return true;    }        public ListNode mergeKLists(ListNode[] lists) {        int k = lists.length;        ListNode head = null;        ListNode cur = null;         while (!over(lists)) {            if (head == null) {                cur = head = head(lists);            } else {                cur.next = head(lists);                cur = cur.next;            }        }        return head;    }}
来看下这个算法的时间复杂度,设数组的大小为k,总的链表的长度为n,每选出一个节点,都要调用一次head函数,head函数的时间复杂度是O(k),一共有n个节点,所以总的时间复杂度是O(n*k)。这个算法并不是一个非常有效率的,在测试的时候也确实超时了。

我们的思路是没有问题的,那么,怎么能够降低这个算法的时间复杂度呢。如果降低head函数的时间复杂度呢?在之前的代码中,head函数是通过遍历的方式来得到最小的节点,所以时间复杂度是O(k)。因为我们每次都只需要最小的节点,而不需要保持整个数组的有序性,所以很容易就可以联想到最小堆。最小堆是这样的一棵二叉树,每个父节点都比左节点和右节点要小。最重要的是当我们取出整个最小堆的根节点,并插入一个新的节点时,所需要的时间复杂度是O(long(k))。这样,我们就把head函数的时间复杂度从O(k)缩小到了O(log(k))。所需要的就是维护一个最小堆。维护最小堆的代码如下:

void rebulidHeap(ListNode[] list, int s) {        int left = 2*s+1;        int right = 2*s+2;        if (left >= list.length)            return;        int small = left;        if (right < list.length && list[right] != null && (list[small] == null || list[right].val < list[small].val))            small = right;        if (list[small] != null && (list[s] == null || list[small].val < list[s].val)) {            swap(list, small, s);            rebulidHeap(list, small);        }    }

在整个程序的开始之初,还需要建堆,把这个数组变成一个最小堆。代码如下:

    void bulidHeap(ListNode[] list) {        for (int i = list.length-1; i > 0; --i) {            if (list[i] == null)                continue;            int parent = (i-1)/2;            if (list[parent] == null || list[i].val < list[parent].val) {                swap(list, i, parent);                rebulidHeap(list, i);            }        }    }
而建堆的时间复杂度是O(k*log(k))。

总体的代码如下:

/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */class Solution {        void rebulidHeap(ListNode[] list, int s) {        int left = 2*s+1;        int right = 2*s+2;        if (left >= list.length)            return;        int small = left;        if (right < list.length && list[right] != null && (list[small] == null || list[right].val < list[small].val))            small = right;        if (list[small] != null && (list[s] == null || list[small].val < list[s].val)) {            swap(list, small, s);            rebulidHeap(list, small);        }    }        void bulidHeap(ListNode[] list) {        for (int i = list.length-1; i > 0; --i) {            if (list[i] == null)                continue;            int parent = (i-1)/2;            if (list[parent] == null || list[i].val < list[parent].val) {                swap(list, i, parent);                rebulidHeap(list, i);            }        }    }        void swap(ListNode[] list, int i, int j) {        ListNode tmp = list[i];        list[i] = list[j];        list[j] = tmp;    }           public ListNode mergeKLists(ListNode[] lists) {        int k = lists.length;        ListNode head = null;        ListNode cur = null;         bulidHeap(lists);        while (lists.length > 0 && lists[0] != null) {            if (head == null) {                cur = head = lists[0];            } else {                cur.next = lists[0];                cur = cur.next;            }            lists[0] = lists[0].next;            rebulidHeap(lists, 0);        }        return head;    }}
再来看下这个算法的时间复杂度,建堆的时间复杂度是O(k*log(k)),合并链表的时间复杂度是O(n*log(k)),所以总的时间复杂度是O((k+n)*log(k))。