LeetCode-Merge k Sorted Lists

算法分析与设计,第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))。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值