【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())

原文地址:https://www.cnblogs.com/ldy-miss/p/12104934.html

时间: 2024-12-16 14:08:29

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

[剑指offer] 41. 数据流中的中位数 (大小堆,优先队列)

对于海量数据与数据流,用最大堆,最小堆来管理. class Solution { public: /* * 1.定义一个规则:保证左边(大顶堆)和右边(小顶堆)个数相差不大于1,且大顶堆的数值都小于等于小顶堆的数 * 2.大小堆顶可以用优先序列实现 插入规则: 当插入数值小于左边的堆顶时候,就插入左边,否则插入右边堆.(注意初始为空时,插入不能比较) 调整使得满足个数差<=1: 正常时是只有两种情况:p=q或者p=q+1,由于每插一个值就会考虑调整,那么边界情况就是p=q+2或者p+1=q p=

[LeetCode] Find Median from Data Stream 找出数据流的中位数

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value. Examples: [2,3,4] , the median is 3 [2,3], the median is (2 + 3) / 2 = 2.5 Design a d

【数据结构】用模版实现大小堆、实现优先级队列,以及堆排序

一.用模版实现大小堆 如果不用模版的话,写大小堆,就需要分别实现两次,但是应用模版的话问题就简单多了,我们只需要实现两个仿函数,Greater和Less就行了,仿函数就是用类实现一个()的重载就实现了仿函数.这个看下代码就能理解了.再设计参数的时候,需要把模版设计成模版的模版参数,因为要实现大小堆嘛!当我们实现好一个大堆或者小队的逻辑后只需要用模版接收的Greater或Less类定义一个变量,就能实现通用功能了. template<typename T> struct Less {     b

堆的实现(大小堆及 优先队列

一.堆的概念 堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构. 堆结构的二叉树存储是: 最大堆:每个父节点的都大于孩子节点. 最小堆:每个父节点的都小于孩子节点. 堆栈中的物体具有一个特性: 最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先出(LIFO)队列. 堆栈中定义了一些操作. 两个最重要的是PUSH和POP. PUSH操作在堆栈的顶部加入一 个元素.POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一. 在此,用vector容器来实现存储,vecto

Leetcode 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) fi

动画 | 什么是平衡二分搜索树(AVL)?

二分搜索树又名有序二叉查找树,它有一个特点是左子树的节点值要小于父节点值,右子树的节点值要大于父节点值.基于这样的特点,我们在查找某个节点的时候,可以采取二分查找的思想快速找到这个节点,时间复杂度期望值是为O(log n),但是它有最坏的的情况下. 例如,输入数组[9,7,5,3,1],如果要满足二分搜索树的规则插入一个个节点,这样的二叉树会退化成一条线性表,待会查找元素的时候时间复杂度已达O(N). 所以针对这种情况,我们引申出了平衡二分搜索树,它每个节点的左右子树高度差不会超过1,它能在O(

[数据结构]二叉搜索树(BST) VS 平衡二叉排序树(AVL) VS B树(平衡多路搜索树) VS B+树 VS 红黑树(平衡二叉B树)

1 二叉排序树/二叉查找树/Binary Sort Tree 1种对排序和查找都很有用的特殊二叉树 叉排序树的弊端的解决方案:平衡二叉树 二叉排序树必须满足的3条性质(或是具有如下特征的二叉树) 若它的左子树不为空,则:左子树上所有结点的值< 它根结点的值 若它的右子树不为空,则:右子树上所有结点的值 > 它根结点的值 它的左子树.右子树也分别为二叉排序树(递归性) (按照如上定义,即: 1 无键值相等的结点 2 中序遍历一颗二叉树时,可得一个结点值递增的有序序列) 2 平衡二叉排序树/Bal

LeetCode Lect7 堆及其应用

概述 堆是一颗完全二叉树.分为大根堆(父节点>=所有的子节点)和小根堆(父节点<=所有的子节点). 插入.删除堆顶都是O(logN),查询最值是O(1). 完全二叉树(Complete Binary Tree) 若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树. 完全二叉树是由满二叉树而引出来的.对于深度为K的,有N个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对

【python-leetcode295-双堆】数据流的中位数

中位数是有序列表中间的数.如果列表长度是偶数,中位数则是中间两个数的平均值. 例如, [2,3,4] 的中位数是 3 [2,3] 的中位数是 (2 + 3) / 2 = 2.5 设计一个支持以下两种操作的数据结构: void addNum(int num) - 从数据流中添加一个整数到数据结构中.double findMedian() - 返回目前所有元素的中位数.示例: addNum(1)addNum(2)findMedian() -> 1.5addNum(3) findMedian() ->