• 【Leetcode 大小堆、二分、BFPRT、二叉排序树、AVL】数据流的中位数(295)


    题目

    中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

    例如,

    [2,3,4] 的中位数是 3

    [2,3] 的中位数是 (2 + 3) / 2 = 2.5

    设计一个支持以下两种操作的数据结构:

    void addNum(int num) - 从数据流中添加一个整数到数据结构中。
    double findMedian() - 返回目前所有元素的中位数。
    示例:

    addNum(1)
    addNum(2)
    findMedian() -> 1.5
    addNum(3) 
    findMedian() -> 2
    

    进阶:

    如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
    如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?
    

    解答

    1,排序——>找中位数,排序用快排,Time: n·log(n),找中位数复杂度O(1)
    2,BFPRT——>没添加一个数,计算一次BFPRT,居然超时了,,,TIme: O(n)
    3,二分插入——>找中位数,二分插入平均log(n),最坏O(n),找中位数O(1)
    4,二叉排序树——>找中位数,构建二叉排序树平均log(n),最差O(n),找中位数O(n)
    5,AVL平衡二叉树——>优化了二叉排序树最差的情况,让树平衡,Time: log(n),Space: O(1),不太好实现
    6,大小堆,一个大堆一个小堆,元素先入大堆,再把大堆最大元素给最小堆,如果此时len(小堆)>len(大堆),则将小堆最小的元素加入大堆,Time: log(n),Space: O(1)

    最优解法:大小堆

    通过代码如下:

    # # 二分
    # from bisect import insort
    #
    #
    # class MedianFinder:
    #
    #     def __init__(self):
    #         self.l = []
    #
    #     def addNum(self, num):
    #         insort(self.l, num)  # 找到位置log(N),插入最差时O(N)
    #
    #     def findMedian(self):
    #         if len(self.l) % 2 != 0:
    #             return self.l[len(self.l) // 2]
    #         else:
    #             return (self.l[len(self.l) // 2] + self.l[len(self.l) // 2 - 1]) / 2
    
    
    # # 二叉排序树做法,插入操作:Time: 平均log(N), 最差O(N),找中位数:O(N),超时了,,,代码应该是没有问题的,
    # # 可以用平衡二叉树(AVL)优化,插入操作:Time: log(N),中位数在根节点,所以找中位数的复杂度是O(1)
    # class BSTstruct:
    #     # BST的节点结构
    #
    #     def __init__(self, key, left, right):
    #         self.key = key
    #         self.left = left
    #         self.right = right
    #
    #
    # class BST:
    #     # 二叉排序树类
    #     def __init__(self):
    #         self.root = None
    #         self.start = 0
    #
    #     def insertBST(self, cur_node, key):
    #         '''
    #         构建二叉排序树
    #         Time: 平均log(N), 最差O(N)
    #         '''
    #         if cur_node == None:  # 空树
    #             self.root = BSTstruct(key, None, None)
    #
    #         elif key <= cur_node.key:  # 这里把相同的元素放在了cur_node的左边
    #             if cur_node.left == None:
    #                 cur_node.left = BSTstruct(key, None, None)
    #                 return
    #             self.insertBST(cur_node.left, key)
    #
    #         elif key > cur_node.key:
    #             if cur_node.right == None:
    #                 cur_node.right = BSTstruct(key, None, None)
    #                 return
    #             self.insertBST(cur_node.right, key)
    #
    #     def findMedian(self, root, cnt):
    #         """
    #         循环着找中位数
    #         """
    #         stack = []
    #         node = root
    #         cur = 0
    #         while stack or node:
    #             while node:
    #                 stack.append(node)
    #                 node = node.left
    #             node = stack.pop()
    #             cur += 1
    #             if cur == cnt:
    #                 return node.key
    #             node = node.right
    #
    #
    # class MedianFinder:
    #     def __init__(self):
    #         self.obj = BST()  # init
    #         self.cnt = 0  # 记录已经插入的个数
    #
    #     def addNum(self, num):
    #         self.obj.insertBST(self.obj.root, num)
    #         self.cnt += 1
    #
    #     def findMedian(self):
    #         '''
    #         遍历到第cnt//2个数就是中位数,偶数的话再往下遍历一个
    #         Time: O(N/2) = O(N)
    #         '''
    #         if self.cnt % 2 == 1:
    #             return self.obj.findMedian(self.obj.root, self.cnt//2+1)
    #         else:
    #             a = self.obj.findMedian(self.obj.root, self.cnt//2)
    #             b = self.obj.findMedian(self.obj.root, self.cnt//2+1)
    #             return (a+b)/2
    
    
    # 最优解法:大小堆, Time: log(n), Space: O(1),优于用二分法维护一个有序列表
    # 两个堆,一个大堆存一半小元素,一个小堆存一半大元素
    # 遍历,先入大堆,把大堆最大的给小堆,如果小堆个数大于大堆,就再把小堆最小给大堆
    # 最终,奇数时,大堆比小堆多一个元素,堆顶就是中位数;偶数时,取两堆顶计算即可
    from heapq import heappop, heappush
    class MedianFinder:
    
        def __init__(self):
            self.small = []
            self.large = []
    
        def addNum(self, num):
            heappush(self.large, -num)
            t = -heappop(self.large)
            heappush(self.small, t)
            if len(self.small) > len(self.large):
                t = heappop(self.small)
                heappush(self.large, -t)
    
        def findMedian(self):
            if len(self.small) < len(self.large):
                return -self.large[0]
            else:
                return (self.small[0]-self.large[0])/2
    
    
    # # BFPRT,Time: O(N), Space: log(N),居然特么超时了,,,
    # class MedianFinder:
    #
    #     def __init__(self):
    #         self.l = []
    #
    #     def addNum(self, num):
    #         self.l.append(num)
    #
    #     def findMedian(self):
    #         length = len(self.l)
    #         if length % 2 == 1:
    #             return self.findKthLargest(self.l, length // 2 + 1)
    #         else:
    #             a = self.findKthLargest(self.l, length // 2)
    #             b = self.findKthLargest(self.l, length // 2 + 1)
    #             return (a + b) / 2
    #
    #     def findKthLargest(self, nums, k):
    #         def getmedian(lis):
    #             """返回序列lis中位数,在BFPRT中就是求每5个数小组的中位数"""
    #             begin = 0
    #             end = len(lis) - 1
    #             sum = begin + end
    #             mid = sum // 2 + sum % 2  # 这个地方加上sum%2是为了确保偶数个数时我们求的是中间两个数的后一个
    #             return sorted(lis)[mid]
    #
    #         def BFPRT(nums, left, right):
    #             """分成每5个数一个小组,并求出每个小组内的中位数"""
    #             num = right - left + 1
    #             offset = 0 if num % 5 == 0 else 1  # 最后如果剩余的数不足5个,我们也将其分成一个小组,和前面同等对待
    #             groups = num // 5 + offset
    #             median = []  # 中位数数组
    #             for i in range(groups):
    #                 begin = left + i * 5
    #                 end = begin + 4
    #                 Median = getmedian(nums[begin:min(end, right) + 1])
    #                 median.append(Median)
    #             return getmedian(median)
    #
    #         def partition(nums, left, right, base):
    #             """在 nums[left, right] 将基准base归位"""
    #             temp = nums[base]
    #             nums[base], nums[right] = nums[right], nums[base]  # 基准和末尾元素互换
    #
    #             max_index = left
    #             for i in range(left, right):  # 把所有小于基准的移到左边
    #                 if nums[i] <= temp:  # 要等于啊!这里好坑的说.. 否则通不过[3, 3, 3, 3, 4, 3, 3, 3, 3]  k = 1
    #                     nums[max_index], nums[i] = nums[i], nums[max_index]
    #                     max_index += 1
    #             nums[right], nums[max_index] = nums[max_index], nums[right]  # 基准归位
    #             return max_index
    #
    #         def select(nums, left, right, k_smallest):
    #             """在 nums[left, right] 找第k小的元素"""
    #             if left == right:  # 递归终止条件
    #                 return nums[left]
    #             base = BFPRT(nums, left, right)
    #             base_index = partition(nums, left, right, nums.index(base))  # 选base为基准,并归位。
    #             if base_index == k_smallest:  # 判断目前已归位的基准,是不是第k_smallest位
    #                 return nums[k_smallest]
    #             elif k_smallest < base_index:  # 递归左半部分
    #                 return select(nums, left, base_index - 1, k_smallest)
    #             else:  # 递归右半部分
    #                 return select(nums, base_index + 1, right, k_smallest)
    #
    #         return select(nums, 0, len(nums) - 1, len(nums) - k)
    #
    
    obj = MedianFinder()
    obj.addNum(1)
    obj.addNum(2)
    print(obj.findMedian())
    obj.addNum(3)
    print(obj.findMedian())
    
  • 相关阅读:
    mondrian4 kylin saiku 整合踩坑记录
    Vector 多字段排序的Java实现
    【linux】重置fedora root密码
    [android错误] Installation error: INSTALL_FAILED_VERSION_DOWNGRA
    [android警告] AndroidManifest.xml警告 Should explicitly set android:allowBackup to true or false
    [android警告]AndroidManifest.xml警告 Not targeting the latest versions of Android
    [android错误] requires API level *
    [android错误] android-support-v*.jar包出现错误。
    [android错误] Failed to install *.apk on device 'emulator-5554': timeout
    mysql乐观锁总结和实践 (转)
  • 原文地址:https://www.cnblogs.com/ldy-miss/p/12104934.html
Copyright © 2020-2023  润新知