美文网首页
LeetCode #23 2018-07-30

LeetCode #23 2018-07-30

作者: 40巨盗 | 来源:发表于2018-07-30 20:31 被阅读0次

23. Merge k Sorted Lists

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

Example:
Input:
[
   1->4->5,
   1->3->4,
   2->6
]
Output: 1->1->2->3->4->4->5->6

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

这道题在分布式系统中非常常见,来自不同client的sorted list要在central server上面merge起来。这道题一般有三种做法,下面一一介绍并分析复杂度。
第一种做法思想最简单,把所有节点值放入一个临时list,然后对list进行排序,即可得到最终的结果。但此方法的时间复杂度和空间复杂度也是最高的。假设总共有k个list,每个list的最大长度是n,这时时间复杂度为O(nklognk), 空间复杂度为O(nk).
代码如下:

    # Solution1 - build-in sort
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        ret = []
        for lst in lists:
            while lst:
                ret.append(lst.val)
                lst = lst.next
        ret.sort()
        dummy = pre = ListNode(0)
        for num in ret:
            cur_node = ListNode(num)
            pre.next = cur_node
            pre = pre.next
        return dummy.next

第二种,有点类似于MergeSort的思路,就是分治法。思路是先分成两个子任务,然后递归求子任务,最后回溯回来。这道题也是这样,先把k个list分成两半,然后继续划分,直到剩下两个list就合并起来,合并时会用到Merge Two Sorted Lists这道题。我们来分析一下上述算法的时间复杂度。同样假设总共有k个list,每个list的最大长度是n,那么运行时间满足递推式T(k) = 2T(k/2)+O(nk)。根据主定理,可以算出算法的总时间复杂度是O(nklogk)。如果不了解主定理的朋友,可以参见[主定理-维基百科]空间复杂度的话是递归栈的大小O(logk)
代码如下:

    # Solution2 - devide & conquer
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        if not lists:
            return None
        if len(lists) == 1:
            return lists[0]
        mid = len(lists) / 2
        head1 = self.mergeKLists(lists[:mid])
        head2 = self.mergeKLists(lists[mid:])
        return self.mergeTwoLists(head1, head2)

    def mergeTwoLists(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        dummy = ListNode(0)
        dummy.next = l1
        pre = dummy
        while(l1 and l2):
            if l1.val <= l2.val:
                l1 = l1.next
            else:
                next = l2.next
                l2.next = pre.next
                pre.next = l2
                l2 = next
            pre = pre.next
        if l2:
            pre.next = l2
        return dummy.next

第三种,用到了堆的数据结构,思路比较难想到,但是其实原理比较简单。维护一个大小为k的堆,每次取堆顶的最小元素放到结果中,然后读取该元素的下一个元素放入堆中,重新维护好。因为每个链表是有序的,每次又是取当前k个元素中最小的,所以当所有链表都读完时结束,这个时候所有元素按从小到大放在结果链表中。这个算法每个元素要读取一次,即是nk次,然后每次读取元素要把新元素插入堆中要logk的复杂度,所以总时间复杂度是O(nklogk)空间复杂度是堆的大小,即为O(k)
代码如下:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # Solution3 - heapq
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        from heapq import heappush, heappop, heapreplace, heapify
        dummy = pre = ListNode(0)
        count = 0
        heap = []
        for node in lists:
            if node:
                heapq.heappush(heap, (node.val, count, node))
            count += 1
        while heap:
            value, temp, node = heap[0]
            if node.next:
                heapreplace(heap, (node.next.val, count, node.next))
                count += 1
            else:
                heappop(heap)
            pre.next = node
            pre = node
        return dummy.next

感悟:
在加入heap之前,可以先判断一下node是否为空,为空代表对应链表已经把所有结点都加入结果链表了,就不用再加入heap中去了。

相关文章

网友评论

      本文标题:LeetCode #23 2018-07-30

      本文链接:https://www.haomeiwen.com/subject/bifamftx.html