---
# System prepended metadata

title: leetcode解題：(Hard) 295. Find Median from Data Stream
tags: [binary search, heap, hard, leetcode]

---

# leetcode解題：(Hard) 295. Find Median from Data Stream

題目：[https://leetcode.com/problems/find-median-from-data-stream/](https://leetcode.com/problems/find-median-from-data-stream/)

描述：實作MedianFinder class，其中addNum()加入新元素，findMedian()找出目前輸入的元素中的中位數

```JAVA=
class MedianFinder {

    public MedianFinder() {
        
    }
    
    public void addNum(int num) {
        
    }
    
    public double findMedian() {
        
    }
}

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

解題思路(1)：使用ArrayList儲存元素，輸入時使用二元搜尋(binary search)找出新元素排序好的位置，找尋中位數時因為輸入時已經排序好了，直接根據list長度為單或雙數回傳對應的中位數即可

程式碼：
```JAVA=
class MedianFinder {
    List<Integer> nums;

    public MedianFinder() {
        nums = new ArrayList<Integer>();
    }
    
    public void addNum(int num) {
        int l = 0, r = nums.size()-1;
        while(l <= r) {
            if(num < nums.get((l+r)/2)) {
                r = (l+r)/2 - 1;
            }
            else l = (l+r)/2 + 1;
        }
        nums.add(l, num);
    }
    
    public double findMedian() {
        if(nums.size() % 2 == 0) {
            return (nums.get(nums.size() / 2) + nums.get(nums.size() / 2 - 1)) / 2.0;
        }
        else return nums.get(nums.size() / 2);
    }
}

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

時間複雜度：addNum()為O(logn (二元搜尋) + n (插入元素)) = O(n)，findMedian()為O(1)
空間複雜度：O(n)

---

解題思路(2)：因為中位數一定是數列的最中間1個元素或2個元素的平均值，我們可以把整個排序的數列拆成較小的那半部(small)與較大的那半部(large)，總元素數是單數時找small的最大值(單數時small會比large多一個元素)，雙數時取出small的最大值與large的最小值取平均值，要快速找最大值跟最小值就能使用資料結構的Max/Min heap。在Java中沒有單純的Heap，因此需要改用底層用Heap實作的Priority Queue。

程式碼：
```JAVA=
class MedianFinder {
    //標準的PriorityQueue優先輸出最小的值
    //計算中位數時需要比較大的那半部輸出其中最小的值
    private PriorityQueue<Integer> large = new PriorityQueue<Integer>();
    //計算中位數時需要比較小的那半部輸出其中最大的值
    //因此建構時輸入Collections.reverseOrder()讓PriorityQueue改為優先輸出最大的值
    private PriorityQueue<Integer> small = new PriorityQueue<Integer>(Collections.reverseOrder());

    private boolean even = true;

    public MedianFinder() {
        
    }
    
    public void addNum(int num) {
        //已有的元素數為雙數，加上num變為單數
        if(even) {
            large.offer(num);               //先丟進large，再將large中最小的丟進small
            small.offer(large.poll());      //small比large多一個元素->中位數會是small裡的最大值
        }
        //已有的元素數為單數，加上num變為雙數
        else {
            small.offer(num);               //要讓small跟large數量平衡，先丟進samll中
            large.offer(small.poll());      //再把small中多出來的最大值丟進large->中位數會是small最大值與large最小值的平均
        }
        even = !even;
    }
    
    public double findMedian() {
        //元素數為雙數->中位數是small最大值與large最小值的平均
        if(even) {
            return (small.peek() + large.peek()) / 2.0;
        }
        //元素數為單數->中位數是small裡的最大值
        else {
            return small.peek();
        }
    }
}

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

時間複雜度：addNum()為O(logn)，findMedian()為O(1)
空間複雜度：O(n)

###### tags: `leetcode` `hard` `binary search` `heap`