您的位置:首页 > 其它

leetcode23. Merge k Sorted Lists总结

2017-08-25 22:57 405 查看
哈哈哈哈, 今天第一次解出了leetcode中的困难题型, 心里有点小激动(>_<), 证明我刷了一个月的leetcode还是有进步滴, 标志着我向一名合格的程序猿迈出了重要的一步。
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
这道题要求将k条有序单链表合并为一条链表。 咋一看, 这道题很难, 根本无从下手, 我刚开始也是懵比了好一会。 合并两条链表很容易, 只需要不断迭代两条链表, 使结果链表指向某个值较小的结点即可, 跟归并排序中的merge函数差不多。 而合并k条链表就不容易了, 因为下一个最小结点不好找, 如果是每次利用遍历法寻找k个结点中的最小结点, 然后不断将k条链表都迭代到最后的话, 这时候的时间复杂度为O(n2)。 通常情况下O(n2)的复杂度都可以优化为O(nlgn)甚至O(n)。 能不能在寻找k个结点中的最小结点上下功夫呢? 当然能! 这时候就引出了第一种做法:


利用最小堆求解

堆是神马东东, 最小堆又是神马东东??

堆就是类似于二叉树的一种数据结构, 但是跟搜索二叉树不同的是, 它的根节点是所有结点中的最大或者最小的结点, 如果根节点是最大的结点, 这个堆就是最大堆; 如果根结点是最小的结点, 这个堆就是最小堆。 大概就是长这个样子:




该图演示了最小堆元素上浮的过程, 将较小元素向上调整到合适的位置; 另外还有一个是下沉, 它将较大元素向下调整位置。

Java类库中有一个称为PriorityQueue的类, 构造函数中可以以堆的大小为第一个参数, 以Comparator为第二个参数, 它规定了元素的比较方式, 可以以此来确定该堆为最小堆或最大堆。
寻找最小元素的复杂度为O(1), 插入元素为O(lgn),  因此总体合并的复杂度为O(nlgn)


public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(lists.length, new Comparator<ListNode>() {
public int compare(ListNode l1, ListNode l2) {
if (l1.val > l2.val)
return 1;
else if (l1.val < l2.val)
return -1;
else
return 0;
}
});
for (ListNode list : lists)
if (list != null)
queue.add(list);
ListNode l = new ListNode(0), p = l;
while (! queue.isEmpty()) {
p.next = queue.poll();
p = p.next;

if (p.next != null)
queue.add(p.next);
}
return l.next;
}


利用归并排序的思路求解

这个解法跟归并排序的解法一毛一样, 就是利用Divide and Conquer的思想将k条链表的排序分解为k/2条链表以及另外k/2条链表, k/2条链表分为两组k/4条链表...然后依次将它们合并起来。 跟合并排序的复杂度一样, 该算法的复杂度同样为O(nlgn)。


public ListNode mergeKLists(ListNode[] lists) {
return divide(lists, 0, lists.length - 1);

}
private ListNode divide(ListNode[] lists, int low, int high) {
if (low == high)
return lists[low];
else if (low < high) {
int mid = (low + high) / 2;
ListNode front = divide(lists, low, mid);;
ListNode back = divide(lists, mid + 1, high);
return merge(front, back);
}
else
return null;
}
private ListNode merge(ListNode front, ListNode back) {
if (front == null) return back;
if (back == null) return front;

if (front.val < back.val) {
front.next = merge(front.next, back);
return front;
}
else {
back.next = merge(front, back.next);
return back;
}

}


总结: Divide and Conquer的思想在很多数组, 链表题总都能适用, 当遇到合并类的问题时, 就该考虑该问题能否先分解为有限个子问题, 将子问题求解之后再合并为最终的解, 通常该解法都能得到最大的优化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode