微信公众号:路人zhang
扫码关注微信公众号

回复“面试手册”,获取本站PDF版

回复“简历”,获取高质量简历模板

回复“加群”,加入程序员交流群

回复“电子书”,获取程序员类电子书

当前位置: 算法 > 剑指offer > 剑指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]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

  • 最多会对 addNum、findMedian 进行 50000 次调用。


算法

(对顶堆) O(n)

小根堆 + 大根堆 结构来维护中位数,大根堆存储比较小的一部分数,小根堆存储比较大的一部分数。 需要维护结构中小根堆的元素个数最多比大根堆元素个数多 1。 如果数的个数是奇数,中位数就是小根堆堆顶,如果是偶数,中位数就是小根堆堆顶和大根堆堆顶之和 / 2。

代码实现技巧:每次先把元素添加到小根堆中,如果小根堆堆顶比大根堆堆顶大,构成逆序就需要交换,且要保证小根堆的元素个数最多比大根堆元素个数多 1,超过 1 则需要将其堆顶弹出并插入到大根堆中。

时间复杂度

每次往堆中插入元素的时间复杂度为 O(1),求中位数的时间也是 O(1),所以总时间复杂度为 O(n)

空间复杂度

O(n)

C++ 代码

class MedianFinder {
public:
    priority_queue<int> max_heap;
    priority_queue<int, vector<int>, greater<int>> min_heap;

    /** initialize your data structure here. */
    MedianFinder() {
        
    }
    
    void addNum(int num) {
        min_heap.push(num);
        if (min_heap.size() > max_heap.size() + 1) {
            max_heap.push(min_heap.top());
            min_heap.pop();
        }
        if (max_heap.size() && min_heap.top() < max_heap.top()) {
            int minv = min_heap.top(), maxv = max_heap.top();
            min_heap.push(maxv), max_heap.push(minv);
            min_heap.pop(), max_heap.pop();
        }
    }
    
    double findMedian() {
        if ((min_heap.size() + max_heap.size()) & 1) return min_heap.top();
        return (min_heap.top() + max_heap.top()) / 2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

Java 代码

class MedianFinder {
    PriorityQueue<Integer> maxHeap;
    PriorityQueue<Integer> minHeap;

    /** initialize your data structure here. */
    public MedianFinder() {
        maxHeap = new PriorityQueue<>((a, b) -> b - a);
        minHeap = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        minHeap.offer(num);
        if (minHeap.size() > maxHeap.size() + 1) {
            maxHeap.offer(minHeap.poll());
        }
        if (!maxHeap.isEmpty() && minHeap.peek() < maxHeap.peek()) {
            int minv = minHeap.poll();
            int maxv = maxHeap.poll();
            minHeap.offer(maxv);
            maxHeap.offer(minv);
        }
    }
    
    public double findMedian() {
        if ((minHeap.size() + maxHeap.size()) % 2 == 1) {
            return minHeap.peek();
        }
        return (minHeap.peek() + maxHeap.peek()) / 2.0;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

Python 代码

class MedianFinder:

    def __init__(self):
        self.max_heap = []
        self.min_heap = []

    def addNum(self, num: int) -> None:
        heapq.heappush(self.min_heap, num)
        if len(self.min_heap) > len(self.max_heap) + 1:
            heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))
        if self.max_heap and self.min_heap[0] < -self.max_heap[0]:
            minv, maxv = heapq.heappop(self.min_heap), -heapq.heappop(self.max_heap)
            heapq.heappush(self.min_heap, maxv)
            heapq.heappush(self.max_heap, -minv)

    def findMedian(self) -> float:
        if (len(self.min_heap) + len(self.max_heap)) % 2 == 1:
            return self.min_heap[0]
        return (self.min_heap[0] - self.max_heap[0]) / 2.0

# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

本文由读者提供Github地址:https://github.com/tonngw


点击面试手册,获取本站面试手册PDF完整版