如何從 5 億個數中找出中位數?
題目描述
從 5 億個數中找出中位數。數據排序後,位置在最中間的數就是中位數。當樣本數爲奇數時,中位數爲 第 (N+1)/2
個數;當樣本數爲偶數時,中位數爲 第 N/2
個數與第 1+N/2
個數的均值。
解答思路
如果這道題沒有內存大小限制,則可以把所有數讀到內存中排序後找出中位數。但是最好的排序算法的時間複雜度都爲 O(NlogN)
。這裏使用其他方法。
方法一:雙堆法
維護兩個堆,一個大頂堆,一個小頂堆。大頂堆中最大的數小於等於小頂堆中最小的數;保證這兩個堆中的元素個數的差不超過 1。
若數據總數爲偶數,當這兩個堆建好之後,中位數就是這兩個堆頂元素的平均值。當數據總數爲奇數時,根據兩個堆的大小,中位數一定在數據多的堆的堆頂。
class MedianFinder {
private PriorityQueue<Integer> maxHeap;
private PriorityQueue<Integer> minHeap;
/** initialize your data structure here. */
public MedianFinder() {
maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
minHeap = new PriorityQueue<>(Integer::compareTo);
}
public void addNum(int num) {
if (maxHeap.isEmpty() || maxHeap.peek() > num) {
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
int size1 = maxHeap.size();
int size2 = minHeap.size();
if (size1 - size2 > 1) {
minHeap.offer(maxHeap.poll());
} else if (size2 - size1 > 1) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
int size1 = maxHeap.size();
int size2 = minHeap.size();
return size1 == size2
? (maxHeap.peek() + minHeap.peek()) * 1.0 / 2
: (size1 > size2 ? maxHeap.peek() : minHeap.peek());
}
}
見 LeetCode No.295:https://leetcode.com/problems/find-median-from-data-stream/
以上這種方法,需要把所有數據都加載到內存中。當數據量很大時,就不能這樣了,因此,這種方法適用於數據量較小的情況。5 億個數,每個數字佔用 4B,總共需要 2G 內存。如果可用內存不足 2G,就不能使用這種方法了,下面介紹另一種方法。
方法二:分治法
分治法的思想是把一個大的問題逐漸轉換爲規模較小的問題來求解。
對於這道題,順序讀取這 5 億個數字,對於讀取到的數字 num,如果它對應的二進制中最高位爲 1,則把這個數字寫到 f1 中,否則寫入 f0 中。通過這一步,可以把這 5 億個數劃分爲兩部分,而且 f0 中的數都大於 f1 中的數(最高位是符號位)。
劃分之後,可以非常容易地知道中位數是在 f0 還是 f1 中。假設 f1 中有 1 億個數,那麼中位數一定在 f0 中,且是在 f0 中,從小到大排列的第 1.5 億個數與它後面的一個數的平均值。
提示,5 億數的中位數是第 2.5 億與右邊相鄰一個數求平均值。若 f1 有一億個數,那麼中位數就是 f0 中從第 1.5 億個數開始的兩個數求得的平均值。
對於 f0 可以用次高位的二進制繼續將文件一分爲二,如此劃分下去,直到劃分後的文件可以被加載到內存中,把數據加載到內存中以後直接排序,找出中位數。
注意,當數據總數爲偶數,如果劃分後兩個文件中的數據有相同個數,那麼中位數就是數據較小的文件中的最大值與數據較大的文件中的最小值的平均值。
方法總結
分治法,真香!
歡迎關注我的公衆號,回覆關鍵字“大禮包” ,將會有大禮相送!!! 祝各位面試成功!!!