剑指offer:数据流中的中位数
admin
2023-07-07 07:24:44
0次

题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

# -*- coding: utf-8 -*-
# @Time         : 2019-07-09 10:29
# @Author       : Jayce Wong
# @ProjectName  : job
# @FileName     : getMedianFromStream.py
# @Blog         : https://blog.51cto.com/jayce1111
# @Github       : https://github.com/SysuJayce

class Solution:
    """
    要求一个数据流中的中位数,由于要求的中位数随着数据流的改变也会发生变化,因此最朴素的解法就是在
    输入数据的时候直接用一个数组保存起来,然后在需要返回中位数的时候先对数组进行排序然后计算中位数。

    但是如果要求中位数,我们其实并不需要得到一个完全有序的数组。如果数组的元素个数是奇数,那么中位
    数就是排序后的中间元素,如果是偶数个,中位数就是排序后中间两个元素的平均值。基于这个观察,如果
    我们能够维护两个指针p1, p2,分别指向当前数组的左右两部分,其中p1指向的是左边部分的最大值,p2
    指向的是右边部分的最小值。如果当前数组个数是奇数个,那么p1和p2指向同一个位置。

    要实现上述的两个指针,我们可以利用堆排序中的知识。
    p1相当于指向最大堆的堆顶,p2相当于指向最小堆的堆顶。
    """
    def __init__(self):
        self.min_heap = []
        self.max_heap = []

    def adjustHeap(self, data, idx):
        """
        对于一个堆,当我们对其进行调整的时候,首先要找到给定节点的子节点,然后判断子节点是否符合
        最大堆/最小堆的要求,如果不符合那就调整。
        :param data: 待调整的堆
        :param idx: 待调整的节点下标
        """
        length = len(data)  # 当前堆的大小
        temp = data[idx]  # 先记录待调整节点的值,最后再放到适当的位置中
        k = 2 * idx + 1  # 左子节点的下标
        while k < length:  # 我们的调整需要在树内调整,因此需要限定下标
            # 这里由于我们有两个堆,而最大堆和最小堆的条件不一样,所以设置这个分支
            if id(data) == id(self.max_heap):
                # 在最大堆的调整中,我们只需要找到左右子节点中最大的值然后跟根节点进行调整
                if k + 1 < length and data[k + 1] > data[k]:
                    k += 1
                # 这里用赋值而非交换,因为待调整节点可能最终并不位于这个位置,而我们之前已经记录
                # 好了待调整节点的值,因此可以放心赋值
                if data[k] > temp:
                    data[idx] = data[k]
                    idx = k  # 赋值完之后需要记录最新的待调整节点可能位于的位置
                # 一旦出现子树符合堆的定义就可以终止调整,因为我们是从下往上构造堆的,只要当前
                # 子树符合定义,那么再往下的子树也符合定义
                else:
                    break
            elif id(data) == id(self.min_heap):
                if k + 1 < length and data[k + 1] < data[k]:
                    k += 1
                if data[k] < temp:
                    data[idx] = data[k]
                    idx = k
                else:
                    break
            # 调整完当前子树之后再调整孙子节点
            k = 2 * k + 1
        # 最后要记得将待调整节点的值放到正确的位置
        data[idx] = temp

    def Insert(self, num):
        # 如果已经有偶数个元素,那么这第奇数个插入到最小堆(右边)
        if (len(self.min_heap) + len(self.max_heap)) & 1 == 0:
            # 在插入最小堆之前,需确认待插入元素比最大堆的所有元素都大(即比最大堆的堆顶元素大)
            # 否则需要先插入最大堆,然后将调整后的最大堆的堆顶挪到最小堆中
            if self.max_heap and num < self.max_heap[0]:
                self.max_heap.append(num)
                num = self.max_heap.pop(0)
                self.adjustHeap(self.max_heap, 0)

            # 插入到最小堆后要调整得到新的堆顶。
            # 由于我们是从0开始构造堆的,所以只需要对堆顶进行调整即可
            self.min_heap.append(num)
            self.adjustHeap(self.min_heap, 0)
        # 如果已经有奇数个元素,那么这第偶数个元素插入到最大堆(左边)
        else:
            if self.min_heap and num > self.min_heap[0]:
                self.min_heap.append(num)
                num = self.min_heap.pop(0)
                self.adjustHeap(self.min_heap, 0)

            self.max_heap.append(num)
            self.adjustHeap(self.max_heap, 0)

    def GetMedian(self, num):
        # 这里num参数是牛客网的OJ有问题,只有这样才能成功调用GetMedian()
        if not self.min_heap:
            raise Exception("No numbers are available.")

        if (len(self.min_heap) + len(self.max_heap)) & 1 == 0:
            return (self.min_heap[0] + self.max_heap[0]) / 2.0
        else:
            return self.min_heap[0]

def main():
    solution = Solution()
    nums = [5, 2, 3, 4, 1, 6, 7, 0, 8]
    for num in nums:
        solution.Insert(num)
        print(solution.GetMedian(num))

if __name__ == '__main__':
    main()

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...