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 以内的答案将被接受。

四、分析: 

由题可知,在一个有序的整数列表中,如果该列表的大小是偶数,则取其两个中位数的平均值;如果该列表的大小是奇数,则取其中位数。由于没有说不可以插入相同值,所以需要一个方法能够使其插入有序的同时,支持插入相同值。取其中位数,有的同学说这不简单,直接取列表大小的中位下标即可轻松得到答案,但事实是这样吗?

请看如下代码: 

typedef struct {
    int *data; //存储数据
    int n; //列表大小
} MedianFinder;

void xun(MedianFinder *obj){ //冒泡排序
    for(int i=0;i<obj->n;i++){
        for(int j=0;j<obj->n-1;j++){
            if(obj->data[j+1]<obj->data[j]){
                int a=obj->data[j+1];
                int b=obj->data[j];
                a=a^b;
                b=a^b;
                a=a^b;
            }
        }
    }
    return;
}
MedianFinder* medianFinderCreate() { //初始化
    MedianFinder *m=(MedianFinder *)malloc(sizeof(MedianFinder));
    m->data=(int *)malloc(sizeof(int)*400);
    m->n=0;
    return m;
}

void medianFinderAddNum(MedianFinder* obj, int num) { //插入数据
    obj->data[obj->n]=num; 
    obj->n+=1;
    return;
}

double medianFinderFindMedian(MedianFinder* obj) { //取中位数
    if(obj==NULL)return 0;
    xun(obj);
    int a=obj->n;
    if(obj->n%2==1){
        return obj->data[a/2];
    }else{
        return (double)(obj->data[a/2-1]+obj->data[a/2])/2; //从0开始
    }
}

void medianFinderFree(MedianFinder* obj) { //释放空间
    if(obj==NULL)return;
    free(obj->data);
    free(obj);
    return;
}

运行如下: 

e71583d8489747538b6cf0ef3c212f38.png 

提交后:

7349dc806bbf45e99faad00752ec2f69.png

错误总结: 

这种思路对于一些简单的测试用例来说,是可以的。但是,对于一些特殊和并不常见的测试用例来说,这种方法是行不通的。

如:

输入:

["MedianFinder","addNum","findMedian","addNum","findMedian","addNum","findMedian",

"addNum","findMedian","addNum","findMedian","addNum","findMedian","addNum",

"findMedian","addNum","findMedian","addNum","findMedian","addNum","findMedian",

"addNum","findMedian"]

 [[],[6],[],[10],[],[2],[],[6],[],[5],[],[0],[],[6],[],[3],[],[1],[],[0],[],[0],[]]

五、使用对顶堆来解决: 

(1)什么是对顶堆:

简单来说,就是在一个列表结构中,该列即具有小顶堆的性质,也具有大顶堆的性质。对顶堆有以下特性

  1. 完全二叉树结构:对顶堆是一棵完全二叉树,即除了最底层外,其他层节点都是满的,最底层的节点从左到右连续排列。

  2. 有序性质:对顶堆中的每个节点,父节点的值不小于(或不大于)子节点的值。这种有序性质可以分为两种类型:最小堆和最大堆。

    • 最小堆:父节点的值小于等于子节点的值,即根节点的值最小。
    • 最大堆:父节点的值大于等于子节点的值,即根节点的值最大。

用途:

对顶堆常用于实现优先队列,能够快速找到最小(或最大)值,并具有高效的插入和删除操作。

 (2)如图所示:

bd16f9dd88d24d4cb12a7e2b1168ee19.png


假如上图中,浅绿色的位置是中位数,其前面(包括自己)是一个大顶堆,其后面是一个小顶堆,拥有这种相似结构的列,也称为对顶堆结构。

思路:假如有一个对顶堆后,如果为空,先向前面的小顶堆插入数据,而后根据要插入的数据的大小与第一个插入的数据相比。如果大了,插入后面,否则插入前面。完成插入操作后,需要检查两个堆的大小,要保持前一个堆为n+1个有序数据,后一个堆为n个有序数据。因为这样子,当列表大小为奇数时,直接返回头部元素即是答案;否则,就将两个堆的头部元素弹出,相加后取平均值即是答案。

六、代码: 

class MedianFinder {
public:
    typedef pair<int,int>pir; //pair用来实现相同数据插入
    set<pir>s1; //set本为小顶堆,有序
    set<pir>s2; //模拟大顶堆,需要将数的符号取反
    int tot; //npc
    MedianFinder() {
        this->tot=0; //npc
    }
    
    void addNum(int num) {
        if(s1.size()==0||num<-s1.begin()->first){ //如果堆为空或插入的数小于第一个数
            s1.insert(pir(-num,tot++)); //插入小顶堆
        }else{
            s2.insert(pir(num,tot++)); //插入大顶堆
        }
        int n1=(s1.size()+s2.size()+1)/2; //堆中有n+1个数
        if(s1.size()==n1)return; //如果相等,返回
        if(s1.size()<n1){ //如果小于n+1
            s1.insert(pir(-s2.begin()->first,tot++)); //则将s2中的头部数据取反,再插入s1
            s2.erase(s2.begin()); //弹出
        }else{
            s2.insert(pir(-s1.begin()->first,tot++)); //否则将s1中的头部数据取反,再插入s2
            s1.erase(s1.begin()); //弹出
        }
        return;
    }
    
    double findMedian() {
        if((s1.size()+s2.size())%2){ //如果为奇数
            return -s1.begin()->first; //直接取反返回头部元素
        }
        double a=-s1.begin()->first;
        double b=s2.begin()->first;
        return (a+b)/2; //否则取两个头部元素相加之后再取平均值
    }
};

输出结果:

d20e2cd613194dec831c531af0fb18e3.png


文章到此结束! 

 

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值