【For Personal Note】【LeetCode 295】【C++】数据流的中位数

LeetCode 295.数据流的中位数

题目

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

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

实现 MedianFinder 类:
MedianFinder() 初始化 MedianFinder 对象。

  • void addNum(int num) 将数据流中的整数 num添加到数据结构中。
  • double findMedian()返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

示例1:

输入
[“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]

这里输入的两个列表,第一个列表表示调用的Method名,调用的Method的对应位置于第二个列表中,表示该Method的参数;
输出的列表,不用多解释,为对应输入位置调用Method的输出结果;

提示:

  • -10^5 <= num <= 10^5
  • 在调用 findMedian之前,数据结构中至少有一个元素
  • 最多 5 * 10^4次调用 addNumfindMedian

思路

两个优先队列或者两个堆,前半段存大一些的数,后半段存小一些的数,比如:

10, 9, 8, 7, 6, 5, 4, 3, 2, 1
前半段:10, 9, 8, 7, 6
后半段:5, 4, 3, 2, 1

当然优先队列和堆的特性,只能反应最大值或最小值在堆头这个节点,前半段这题以小顶堆存储,堆顶是6;后半段是大顶堆存储,堆顶是5;
堆结构上可以看作是一个二叉树,存储上是连续的数组形式(至少手写堆是)
堆顶下面同层的节点,没有谁一定比谁大或小之说,比如第二层的两个节点,他们是堆顶这个根节点的左节点和右节点,并不能确定这个位置谁一定大,谁一定小;这个位置随着不停地更换,因为add值和delete值的缘故,左节点的位置的值,可能比右节点的值大,也可能小;
唯一能确定的是,这个节点,比他下面的所有子孙节点大或小;大顶堆,这个节点比他下面的所有子孙节点大;小顶堆,这个节点比他下面的所有子孙节点小;
findMedian查找时,
存储的总数为偶数时,取前半段的最小值,和后半段的最大值,相加除以2;
存储的总数为奇数时,取后半段的最大值,即为中位数

大顶堆的顶部,是存储的这组数的最大值;大顶堆用作这题的后半段数据存储;
小顶堆的顶部,是存储的这组数的最小值;小顶堆用作这题的前半段数据存储;
(使用优先队列priority_queue是一模一样的,后面直接在不同的实现方式上贴代码)
addNum添加时,
加进来前,已存储的总数为偶数时,
先跟前半段的小顶堆比较,堆顶和新加的num,总有一个需要加到后半段;
加进来前,已存储的总数为奇数时,
先跟后半段的大顶堆比较,堆顶和新加的num,总有一个需要加到前半段;
按照这个逻辑,可以在findMedian时,找到中位数;

当然,在加进来num时,总数为0,也就是第一次加入num时,我们直接将其加入到后半段的堆货有限队列中;

实现方式

这个时间效率是比后两种方式更快的

#define MAXHEAP 50002
// 最多调用50000次addNum和findMedian,假设都是addNum,最多存储int heap[50000]
// 分为两个堆,我自己每次都是从heap[1]开始存,这样heap[idx],idx>>1就能直接得到左节点
// 且位运算是块于+ -运算的
// 前后堆最多25000, 25001个数,都是从heap[1]开始存,空间就需要25001,25002个int
// 实际申请的时候,我是直接50002/2,因为这是查找中位数的题,肯定至少有一次findMedian的调用,所以后半段少申请一位没关系
// 细纠结这个heap大小没必要,但是纠结完了,我神清气爽^0^

inline bool CmpForSmallHeap(int a, int b) {
    return a < b;
}

inline bool CmpForBigHeap(int a, int b) {
    return b < a;
}

struct Heap {
    int N;
    bool (*Cmp)(int, int);
    int heap[MAXHEAP/2];
    void init(int preOrBack) {
        N = 0;
        // 0, pre -> small heap; 1, back -> big heap
        Cmp = preOrBack ? &CmpForBigHeap : &CmpForSmallHeap;
    }

    void swap(int idxa, int idxb) {
        int temp = heap[idxa];
        heap[idxa] = heap[idxb];
        heap[idxb] = temp;
    }

    void up(int idx){
        while (idx > 1 && Cmp(heap[idx], heap[idx >> 1])) {
            swap(idx, idx>>1);
            idx >>= 1;
        }
    }

    void down(int idx){
        int child;
        while ((child = idx << 1) <= N) {
            if (child < N && Cmp(heap[child + 1], heap[child])) child++;
            if (Cmp(heap[child], heap[idx])) {
                swap(child, idx);
                idx = child;
            }
            else return;
        }
    }

    void add(int one){
        heap[++N] = one;
        up(N);
    }

    int pop() {
        int temp = heap[1];
        swap(1, N--);
        down(1);
        return temp;
    }
};

class MedianFinder {

public: 
    Heap ph, bh;    // pre heap; back heap;
public:
    MedianFinder() {
        this->ph.init(0);
        this->bh.init(1);
    }

    void addNum(int num) {
        // 加进来前,总数:
        // 奇 -> 跟bh[1]比,谁上去
        // 偶 -> 跟ph[1]比,谁下去
        int total = ph.N + bh.N;
        if (total == 0) bh.add(num);
        else {
            if (total % 2 != 0) {
                if (num >= bh.heap[1]) {
                    ph.add(num);
                }
                else {
                    ph.add(bh.pop());
                    bh.add(num);
                }
            } // if (total % 2 == 0)
            else {
                if (num < ph.heap[1]) {
                    bh.add(num);
                }
                else {
                    bh.add(ph.pop());
                    ph.add(num);
                }
            }
        }
    }

    double findMedian() {
        int total = ph.N + bh.N;
        if (total % 2 == 0) {
            return (ph.heap[1] + bh.heap[1]) / 2.0;
        }
        else {
            return bh.heap[1];
        }
    }
};

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

后两种实现方式是我看了官方题解,是同样的思路,用的库的方式(queue头文件的priority_queue, set头文件的multiset)实现的,我以自己的方式写了一遍,但也是没什么大的差别;
库的优点是代码量少,我们只要注重逻辑的算法就可以了;

优先队列

#include <queue>
#include <iostream>
using namespace std;

class MedianFinder {
public:
    priority_queue<int, vector<int>, greater<int>> ph;  // pre heap
    priority_queue<int, vector<int>> bh;    // back heap
public:
    MedianFinder() {
        
    }

    void addNum(int num) {
        int total = ph.size() + bh.size();
        if (total == 0) bh.push(num);
        else {
            if (total % 2 != 0) {
                if (num >= bh.top()) {
                    ph.push(num);
                }
                else {
                    ph.push(bh.top()); bh.pop();
                    bh.push(num);
                }
            }   // total % 2 == 0
            else {
                if (ph.top() >= num) {
                    bh.push(num);
                }
                else {
                    bh.push(ph.top()); ph.pop();
                    ph.push(num);
                }
            }
        }
    }

    double findMedian() {
        int total = ph.size() + bh.size();
        if (total % 2 != 0) {
            return bh.top();
        }
        else {
            return (ph.top() + bh.top()) / 2.0;
        }
    }
};

multiset容器

multiset是可以存储重复元素,自动排序的;
set也可以自动排序,但是不能存储值相同的元素;
所以这题multiset容器适合,
因为涉及排序,效率也是这三种方式,时间效率较慢的一个;

#include <set>

class MedianFinder {

public:
    multiset<int, less<int>> ph;
    multiset<int, greater<int>> bh;

public:
    MedianFinder() {

    }

    void addNum(int num) {
        int total = ph.size() + bh.size();
        if (total == 0) bh.insert(num);
        else {
            if (total % 2 != 0) {
                if (num >= *bh.begin()) {
                    ph.insert(num);
                }
                else {
                    ph.insert(*bh.begin()); bh.erase(bh.begin());
                    bh.insert(num);
                }
            }   // if total % 2 == 0
            else {
                if (num <= *ph.begin()) {       // !!!bh.begin() -> ph.begin()
                    bh.insert(num);
                }
                else {
                    bh.insert(*ph.begin()); ph.erase(ph.begin());   //bh.insert(*bh.begin()); -> bh.insert(*ph.begin());
                    ph.insert(num);
                }
            }
        }
    }

    double findMedian() {
        int total = ph.size() + bh.size();
        if (total % 2 == 0) {
            return (*ph.begin() + *bh.begin()) / 2.0;
        }
        else {
            return *bh.begin();
        }
    }
};
  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值