76.数据流的中位数

58 阅读2分钟

题目链接

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。

解法1 暴力解法

思路

想要快速的找到中位数,最简单的办法就是每次插入都进行排序,最简单的排序就是插入排序。

具体思路就是每次插入找出该数字应该在的正确大小索引,找中位数则根据数组长度奇偶来返回。

代码

class MedianFinder {
    private arr: number[];

    constructor() {
        this.arr = [];
    }

    addNum(num: number): void {
        // 插入排序:找到插入位置
        let left = 0, right = this.arr.length;
        while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (this.arr[mid] < num) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        this.arr.splice(left, 0, num); // 插入
    }

    findMedian(): number {
        const n = this.arr.length;
        if (n % 2 === 1) {
            return this.arr[Math.floor(n / 2)];
        } else {
            const mid = n / 2;
            return (this.arr[mid - 1] + this.arr[mid]) / 2;
        }
    }
}

时空复杂度

时间复杂度:初始化和 findMedian 都是 O(1)addNumO(q),其中 qaddNum 的调用次数

空间复杂度:O(q)

解法2 大小堆

思路

我们利用堆顶存放数据的特点可以来优化一下。分别构建一个大根堆 A 和一个小根堆 B ,始终保持大根堆的元素个数大于等于小根堆的元素个数。

插入逻辑保持如下规则:

  1. 如果当前 A.size() !== B.size():说明 A 的元素比 B 多。为了维持平衡,我们应往 B 插入一个数。 但为了保证堆的有序性,我们:先把新数放入 A 然后把 A 的堆顶(最大值)弹出,插入 B。这样大的值“推”到了右边的小根堆中,保证中位数分布合理。

  2. 如果 A.size() === B.size():当前两边平衡,新数可以先进入右边(B)。然后从 B 弹出最小的值,插入 A(让左边继续保持比右边多一个)

代码

class MedianFinder {
    private left;
    private right;

    constructor() {
        this.left = new MaxPriorityQueue();
        this.right = new MinPriorityQueue();
    }

    addNum(num: number): void {
        if (this.left.size() === this.right.size()) {
            this.right.enqueue(num);
            this.left.enqueue(this.right.dequeue());
        } else {
            this.left.enqueue(num);
            this.right.enqueue(this.left.dequeue());
        }
    }

    findMedian(): number {
        if (this.left.size() > this.right.size()) {
            return this.left.front();
        }
        return (this.left.front() + this.right.front()) / 2;
    }
}

时空复杂度

时间复杂度:初始化和 findMedian 都是 O(1)addNumO(logq),其中 qaddNum 的调用次数。每次操作堆需要 O(logq) 的时间。

空间复杂度:O(logq)