【剑指offer】JZ41 数据流中的中位数

文章介绍了如何在数据流中实时计算中位数,提供了两种方法:一种是利用插入排序,保持数组有序;另一种是使用堆排序,维护两个堆分别存储最大和最小的一半元素。两种方法的时间复杂度均为O(nlogn),空间复杂度为O(n)。
摘要由CSDN通过智能技术生成

1 问题

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

数据范围:数据流中数个数满足 1≤n≤1000 ,大小满足 1≤val≤1000
进阶: 空间复杂度 O(n) , 时间复杂度 O(nlogn)

示例1
输入:[5,2,3,4,1,6,7,0,8]
返回值:"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
说明:数据流里面不断吐出的是5,2,3…,则得到的平均数分别为5,(5+2)/2,3…

示例2
输入:[1,1,1]
返回值:"1.00 1.00 1.00 "

2 答案

自己写的,利用了sort()

class Solution:
    def __init__(self):
        self. num_list = []
    def Insert(self, num):
        return self.num_list.append(num)
        
    def GetMedian(self):
        self.num_list.sort()
        if len(self.num_list) == 1:
            return self.num_list[0]
        if len(self.num_list) % 2 == 1:
            return self.num_list[len(self.num_list) // 2]
        else:
            return ((self.num_list[len(self.num_list) // 2 - 1] + self.num_list[len(self.num_list) // 2]) / 2)      

在这里插入图片描述

官方解

  1. 插入排序法

插入排序是排序中的一种方式,一旦一个无序数组开始排序,它前面部分就是已经排好的有序数组(一开始长度为0),而其后半部分则是需要排序的无序数组,插入排序的做法就是遍历后续需要排序的无序部分,对于每个元素,插入到前半部分有序数组中属于它的位置——即最后一个小于它的元素后。

  • step 1:用一数组存储输入的数据流。
  • step 2:Insert函数在插入的同时,遍历之前存储在数组中的数据,按照递增顺序依次插入,如此一来,加入的数据流便是有序的。
  • step 3:GetMedian函数可以根据下标直接访问中位数,分为数组为奇数个元素和偶数个元素两种情况。记得需要类型转换为double。
class Solution:
    def __init__(self):
        self.val = []
    def Insert(self, num):
        if len(self.val) == 0:
            self.val.append(num)
        else:
            i = 0
            while i < len(self.val):
                if num <= self.val[i]:
                    break
                i += 1
            self.val.insert(i, num)
    def GetMedian(self):
        n = len(self.val)
        if n % 2 == 1: 
            return self.val[n // 2] 
        else: 
            return (self.val[n // 2] + self.val[n // 2 - 1]) / 2.0
  1. 堆排序

除了插入排序,我们换种思路,因为插入排序每次要遍历整个已经有的数组,很浪费时间,有没有什么可以找到插入位置时能够更方便。

我们来看看中位数的特征,它是数组中间个数字或者两个数字的均值,它是数组较小的一半元素中最大的一个,同时也是数组较大的一半元素中最小的一个。那我们只要每次维护最小的一半元素和最大的一半元素,并能快速得到它们的最大值和最小值,那不就可以了嘛。这时候就可以想到了堆排序的优先队列。

  • step 1:我们可以维护两个堆,分别是大顶堆min,用于存储较小的值,其中顶部最大;小顶堆max,用于存储较大的值,其中顶部最小,则中位数只会在两个堆的堆顶出现。
  • step 2:我们可以约定奇数个元素时取大顶堆的顶部值,偶数个元素时取两堆顶的平均值,则可以发现两个堆的数据长度要么是相等的,要么奇数时大顶堆会多一个。
  • step 3:每次输入的数据流先进入大顶堆排序,然后将小顶堆的最大值弹入大顶堆中,完成整个的排序。
  • step 4:但是因为大顶堆的数据不可能会比小顶堆少一个,因此需要再比较二者的长度,若是小顶堆长度小于大顶堆,需要从大顶堆中弹出最小值到大顶堆中进行平衡。
import heapq
class Solution:
    def __init__(self):
        self.max = [] 
        self.min = [] 
    def Insert(self, num):
        heapq.heappush(self.min, (-1*num))
        heapq.heappush(self.max, (-1*self.min[0]))
        heapq.heappop(self.min)
        if len(self.min) < len(self.max):
            heapq.heappush(self.min, -1*self.max[0])
            heapq.heappop(self.max)
    def GetMedian(self):
        if len(self.min) > len(self.max):
            return self.min[0] * -1.0
        else:
            return (self.min[0] * -1.0 + self.max[0]) / 2

https://www.nowcoder.com/share/jump/9318638301699102793641

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LouHerGetUp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值