Merge k Sorted Lists

不想你离开。 提交于 2019-12-20 18:59:10

https://oj.leetcode.com/problems/merge-k-sorted-lists/

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

解题思路:

首先想到的思路很generic,遍历k个链表的第一个节点,找出最小的那个,加入待返回的链表,同时这个节点往后一个,其他不动,然后再这样比较。在这个过程中,如果哪个链表的节点已经到了最后,就在lists里面删去他,这样直到lists为空就可以了。

这个过程中需要注意几点。第一是,在lists的循环内,进行remove操作,是有问题的。因为已经改变了lists的size,这样i其实已经往后走一个了,必须也i--。否则,就要使用iterator,这个迭代器可以避免这个问题。

第二就是,lists中获取某个节点,往后迭代,还要重新写回lists中,需要用到List.set(index, Object)的方法。

类似于lists.get(begin) = merge2Lists(lists.get(begin), lists.get(end));的写法有什么不对?因为左侧不是一个variable,而是一个对象,怎么能给他赋值?这时一个很初级却比较容易犯的错误。

假设有k个链表,最大的链表有n个元素,这个算法是需要k*nk的时间的,会超时。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode mergeKLists(List<ListNode> lists) {
        ListNode mergedNode = new ListNode(Integer.MAX_VALUE);
        ListNode headNode = mergedNode;
        
        while(lists.size() > 0){
            int minNodeIndex = 0;
            for(int i = 0; i < lists.size(); i++){
                if(lists.get(i) == null){
                    lists.remove(i);
                    i--;
                    continue;
                }
            
                int min = Integer.MAX_VALUE;
            
                if(lists.get(i).val < min){
                    minNodeIndex = i;
                }
            }
            if(lists.size() == 0){
                break;
            }
            mergedNode.next = lists.get(minNodeIndex);
            mergedNode = mergedNode.next;
            lists.set(minNodeIndex, lists.get(minNodeIndex).next);
        }
        return headNode.next;
    }
}

然后想到,可以把这kn个节点全部放入数组中,排序,然后形成新的链表,时间复杂度为O(knlog(kn)),空间复杂度为O(kn)。

也可以采用堆而不是数组的数据结构。建造一个最小堆,堆的大小为k。将本次遍历的k个元素放入堆,取出堆顶的元素,也就是当前最小的元素,然后和上面的步骤一样。因为堆的取出顶元素的时间为O(1),insert时间为O(logn),n为堆的大小。这样可以把kn*n的时间节省到kn*logk,空间复杂为O(k)。

在java语言中,PriorityQueue的类就是最小堆的实现。在实际实现的过程中,需要重写compareTo的方法,有两种。一种是让ListNode类去实现Comparable的接口,然后override它的compareTo()方法。这种方法要求修改ListNode类,不太可行。第二种就是自己写一个Compare implements Comparator<T>的类,去override它的compare()方法。然后建立PriorityQueue的时候,用new PriorityQueue(size, new Compare())去构造一个最小堆。

借助内部类,可以去尝试实现第二种方法,下面是具体的代码。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public class Compare implements Comparator<ListNode>{
        public int compare(ListNode l1, ListNode l2){
            return l1.val - l2.val;
        }
    }
    
    public ListNode mergeKLists(List<ListNode> lists) {
        if(lists.size() == 0){
            return null;
        }
        
        ListNode headNode = new ListNode(0);
        ListNode iterateNode = headNode;
        PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(lists.size(), new Compare());
        
        for(int i = 0; i < lists.size(); i++){
            if(lists.get(i) == null){
                lists.remove(i);
                i--;
                continue;
            }
            queue.add(lists.get(i));
        }
        
        while(queue.size() > 0){
            iterateNode.next = queue.poll();
            iterateNode = iterateNode.next;
            if(iterateNode.next != null){
                queue.add(iterateNode.next);
            }
        }
        
        return headNode.next;
    }
}

上面的方法是AC的。

又想到借助于merge2Lists的方法,从第一个链表开始,每次merge下一个,两两归并,也就是在外面套了一层循环。很可惜,这种解法也是超时的。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode mergeKLists(List<ListNode> lists) {
        ListNode headNode = null;

        for(int i = 0; i < lists.size(); i++){
            headNode = merge2Lists(headNode, lists.get(i));
        }
        return headNode;
    }
    
    public ListNode merge2Lists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        ListNode dummy = new ListNode(0);
        ListNode returnNode = dummy;
        
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                dummy.next = l1;
                dummy = dummy.next;
                l1 = l1.next;
            }else {
                dummy.next = l2;
                dummy = dummy.next;
                l2 = l2.next;
            }
        }
        
        if(l1 == null){
            dummy.next = l2;
        }
        if(l2 == null){
            dummy.next = l1;
        }
        return returnNode.next;
    }
}

为了解决超时的方法,这里使用两两归并排序的方法。使用两个指针,指向头尾节点,然后对他们排序,将排序得到的链表放入首节点的位置,然后两个节点往中间靠,直到首尾指针相遇。这时尾指针不动,首指针从0再开始循环,重复上述过程,直到尾指针到0。这时返回首元素,就是归并好的链表。

这个方法的时间复杂度为O(kn*logk)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode mergeKLists(List<ListNode> lists) {
        if(lists.size() == 0){
            return null;
        }
        int end = lists.size() - 1;
        int begin = 0;
        
        while(end > 0){
            begin = 0;
            while(begin < end){
                lists.set(begin, merge2Lists(lists.get(begin), lists.get(end)));
                begin++;
                end--;
            }
        }
        return lists.get(0);
    }
    
    public ListNode merge2Lists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        ListNode dummy = new ListNode(0);
        ListNode returnNode = dummy;
        
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                dummy.next = l1;
                dummy = dummy.next;
                l1 = l1.next;
            }else {
                dummy.next = l2;
                dummy = dummy.next;
                l2 = l2.next;
            }
        }
        
        if(l1 == null){
            dummy.next = l2;
        }
        if(l2 == null){
            dummy.next = l1;
        }
        return returnNode.next;
    }
}

总结一下,这道题有两种解法,一种是利用最小堆的数据结构,另一种利用merge2Lists的方法,而且在中间采用了二分排序,每次两两merge,也可以解决问题。

这两种解法虽然时间复杂度都是O(kn*logk),但是思路上完全是两个方向,值得好好体会。特别是在解决一个问题,时间复杂度很高的时候,如果去降低它。我们看到可以借助更好的数据结构,比如堆,但是要花费一些额外的空间,可是能够很简便的解决比较复杂的问题。在不允许使用这种已有的数据结构时,多数是可以采用折半或者两两归并的方法,借助已有的方法,取解决问题,将线性的时间复杂度降低到对数时间。

参考文章:

http://bangbingsyb.blogspot.jp/2014/11/leetcode-merge-k-sorted-lists.html

http://fmarss.blogspot.jp/2014/09/leetcode-solution_11.html

http://blog.csdn.net/linhuanmars/article/details/19899259

https://oj.leetcode.com/discuss/26/is-the-complexity-o-kn

https://oj.leetcode.com/discuss/23855/java-solution-without-recursion-feel-free-to-comment

update 2015/06/21:

三刷,上面归并算法的一个递归的写法

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0) {
            return null;
        }
        return mergeHelper(lists, 0, lists.length - 1);
    }
    
    public ListNode mergeHelper(ListNode[] lists, int start, int end) {
        if(start == end) {
            return lists[start];
        }
        if(start > end) {
            return null;
        }
        int mid = start + (end - start) / 2;
        ListNode left = mergeHelper(lists, start, mid);
        ListNode right = mergeHelper(lists, mid + 1, end);
        return mergeTwoLists(left, right);
    }
    
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode res = dummy;
        while(l1 != null && l2 != null) {
            if(l1.val <= l2.val) {
                dummy.next = l1;
                l1 = l1.next;
                dummy = dummy.next;
            } else {
                dummy.next = l2;
                l2 = l2.next;
                dummy = dummy.next;
            }
        }
        if(l1 == null) {
            dummy.next = l2;
        } else {
            dummy.next = l1;
        }
        return res.next;
    }
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!