1. 程式人生 > 實用技巧 >劍指offer.41 資料流中的中位數

劍指offer.41 資料流中的中位數

這道題涉及到對堆這個資料結構的使用,落實到程式碼上實際使用的是優先佇列(優先佇列底層可以通過堆來實現)。

題目描述

如何得到一個數據流中的中位數?如果從資料流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從資料流中讀出偶數個數值,那麼中位數就是所有數
值排序之後中間兩個數的平均值。

例如,

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

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

設計一個支援以下兩種操作的資料結構:
void addNum(int num) - 從資料流中新增一個整數到資料結構中。
double findMedian() - 返回目前所有元素的中位數。

示例:
輸入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
輸出:[null,null,null,1.50000,null,2.00000]

解析

這道題需要設計一個數據結構來完成快速的中位數的獲取。
當然了,一個最基礎的想法就是每次插入新資料之後都對資料進行排序,然後獲取中位數。但這樣的代價太大了,明顯不可取。

為此,使用兩個堆來儲存不斷流入的資料流,一個大根堆和一個小根堆。

其中,大根堆中的資料要始終小於小根堆中的資料,大根堆的堆頂資料是該堆中資料的最大值,小根堆的堆頂是該堆中資料的最小值。當資料總數為奇數時,小根堆資料數量比大根堆數量多1,當總數為偶數時,兩個堆資料量一致。

當要獲取資料中位數時:

  • 如果資料總數為奇數,則取小根堆堆頂。
  • 如果資料總數為偶數,則取(大根堆堆頂 + 小根堆堆頂)/ 2

code

class MedianFinder {
    // 使用大根堆和小根堆
    private PriorityQueue<Integer> min;
    private PriorityQueue<Integer> max;
    private int count;

    /** initialize your data structure here. */
    public MedianFinder() {
        min = new PriorityQueue<>();
        max = new PriorityQueue<>((Integer a, Integer b) -> {return b - a;});
        count = 0;
    }
    
    public void addNum(int num) {
        count++;
        // 如果是奇數個,則小根堆多一個,偶數個則一樣多
        if (count % 2 == 1) {
            max.offer(num);
            min.offer(max.poll());
        } else {
            min.offer(num);
            max.offer(min.poll());
        }
    }
    
    public double findMedian() {
        if (count % 2 == 0)
            return (min.peek() + max.peek()) / 2.0;
        return (double)min.peek();
    }
}