剑指 Offer 41. 数据流中的中位数解析

剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

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

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

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

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

示例 1:

输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

解决方案:

由题目得,数据流是将数值一个一个地输入程序,即过程中,中位数的变化是逐元素的。

其过程如下所示,用红色表示中位数节点:

NULL
1
1
2
1
2
3
1
2
3
4

从元素的逐个添加过程,我们可以发现,当我们将已有的n个数据分解为两部分, S s m a l l = { x ∣ x < = 中位数 } , S b i g = { x ∣ x > 中位数 } S_{small}=\{x|x<=中位数\},S_{big}=\{x|x>中位数\} Ssmall={ xx<=中位数},Sbig={ xx>中位数}

当 n 为奇数时, l e n ( S s m a l l ) = n + 1 2 , l e n ( S b i g ) = n − 1 2 ; 当 n 为偶数时, l e n ( S s m a l l ) = n 2 , l e n ( S b i g ) = n 2 ; 当n为奇数时,len(S_{small}) = \frac{n+1}{2},len(S_{big})=\frac{n-1}{2};\newline 当n为偶数时,len(S_{small}) = \frac{n}{2},len(S_{big})=\frac{n}{2}; n为奇数时,len(Ssmall)=2n+1len(Sbig)=2n1;n为偶数时,len(Ssmall)=2nlen(Sbig)=2n;

若我们能够通过算法使得在数据插入后,仍能将已有数据管理为上述结构,则当我们想要获取中位数时,中位数为:

设 A 为 S s m a l l 集中的最大值 , B 为 S b i g 中的最小值 , 则: 当 n 为奇数时,中位数 = A 当 n 为偶数时,中位数 = A + B 2 设A为S_{small}集中的最大值,B为S_{big}中的最小值, 则:\newline 当n为奇数时,中位数=A \newline 当n为偶数时,中位数=\frac{A+B}{2} ASsmall集中的最大值,BSbig中的最小值,则:n为奇数时,中位数=An为偶数时,中位数=2A+B

此时,我们将问题转化,如何将数据管理成上述结构;

从元素数量的角度出发,可以发现,当n为奇数时,应向 S b i g S_{big} Sbig中添加元素;n为偶数时,应向 S s m a l l S_{small} Ssmall中添加元素,并使得两个集合中的元素仍满足定义。

进一步地,问题转为如何插入使得集合中元素仍满足定义。

新进元素C有以下情况:

  1. C ≤ A
  2. A < C < B
  3. B≤C

当n为奇数时,对于以上三种情况,需要执行以下操作

  1. 将C插入 S s m a l l S_{small} Ssmall
  2. 将C插入 S s m a l l S_{small} Ssmall
  3. 将B取出,插入 S s m a l l S_{small} Ssmall, 再将C插入 S b i g S_{big} Sbig

值得注意的是,存在A≤B的关系,则当将C插入 S b i g S_{big} Sbig时,此时 S b i g S_{big} Sbig的最小值new_B的值为:

  1. new_B = C
  2. new_B = C
  3. new_B = B

所以上述操作可以简写为,将C插入 S b i g S_{big} Sbig,将new_B取出插入 S s m a l l S_{small} Ssmall

当n为偶数时,对于以上三种情况,需要执行以下操作:

  1. 将A取出,插入 S b i g S_{big} Sbig, 将C插入 S s m a l l S_{small} Ssmall
  2. 将C插入 S b i g S_{big} Sbig
  3. 将C插入 S b i g S_{big} Sbig

与n为奇数时,同理,该操作可简写为,将C插入 S s m a l l S_{small} Ssmall,将new_A取出插入 S b i g S_{big} Sbig

由于我们需要频繁地从 S s m a l l S_{small} Ssmall取出最大值,从 S b i g S_{big} Sbig取出最小值,所以我们可以使用大顶堆和小顶堆分别维护 S s m a l l S_{small} Ssmall S b i g S_{big} Sbig,****堆的实现代码可以看博客:堆的图示以及代码实现**。

实现代码如下:

class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.min_heap = Heap(lambda x,y:x<=y)
        self.max_heap = Heap(lambda x,y:x>=y)

    def addNum(self, num: int) -> None:
        if len(self.min_heap) != len(self.max_heap):
            self.min_heap.insert(num)
            self.max_heap.insert(self.min_heap.delete())
        else:
            self.max_heap.insert(num)
            self.min_heap.insert(self.max_heap.delete())

    def findMedian(self) -> float:
        # return [self.min_heap.store,[],self.max_heap.store,[],[]]
        return self.min_heap[0] if len(self.min_heap) != len(self.max_heap) else (self.min_heap[0] + self.max_heap[0])/2.0

猜你喜欢

转载自blog.csdn.net/YmgmY/article/details/129333182