数据流中的中位数

如何得到一个数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

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

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

来源:力扣(LeetCode)


一、示例

示例1:

输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
[“MedianFinder”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:
最多会对 addNum、findMedian 进行 50000 次调用。

二、解题方法

1.菜鸟解题(我)

在看到题的时候,我想我拿一个容器储存传进来的值,
当查询中位数时我就排序好,
当容器长度为奇数时取len/2,偶数时取(len/2和len/2-1)
那这题不就迎刃而解了吗

class MedianFinder {
 	List<Integer> res;
    /** initialize your data structure here. */
    public MedianFinder() {
        res =new ArrayList<>();
    }
    
    public void addNum(int num) {
        res.add(num);
    }
    
    public double findMedian() {
        Collections.sort(res);
        int mid = res.size()/2;
        // if(res.size()%2==1){
        //     
        // }else if(res.size()%2==0){
        //     return (res.get(mid)+res.get(mid-1))/2.0;
        // }
        return res.size()%2!=1 ? 
        (res.get(mid)+res.get(mid-1))/2.0
        :res.get(mid);
    }
}

嗯确实是。。。
leetcode运行结果
那么恭喜你,如果在面试中这题这么写,
不出意外的话
面试官会和你说:“ 回去等通知吧!”

2.优先队列/堆(参考力扣用户Krahets的讲解)

先简单介绍一下数据结构堆:
堆通常是一个可以被看做一棵完全二叉树的数组对象。
堆分为两种:大顶堆和小顶堆,两者的差别在于节点的排序方式。
大顶堆:堆顶存放最大值(小顶堆反之)

堆的常用方法:
构建优先队列
支持堆排序
快速找出一个集合中的最小值(或者最大值)
在朋友面前装逼

回到这题来:
能不能分别用一个小顶堆和一个大顶堆来各自存放数组中的较大一半数组与较小一半数组呢?
比如数组1,2,3,4,5
设a是小顶堆,存放较大的一半(最小值为堆顶)
b是大顶堆,存放较小的一半(最大值为堆顶)
那么
a:5,4,3(堆顶是3)
b:2,1(堆顶是2)
那么问题来了,
怎么保证a,b存放的是较小或较大的一半呢?
中位数怎么取呢?
两个问题其实是一并解决的

前提:当size相同咱们优先存放新增的num到a中(长度奇数时直接看a堆顶)
当a.size()等于b.size()时,
通过把num添加到b中, 再从b中弹出最大值添加到a中。
当size不相等,
通过把num添加到a中, 再从a中弹出最小值添加到b中。
当a.size()==b.size()即为偶数
取a堆顶+b堆顶除以2
!=时即为奇数
取a堆顶

class MedianFinder {
    Queue<Integer> a,b;
    /** initialize your data structure here. */
    public MedianFinder() {
        a=new PriorityQueue<>();//小顶堆  较大的一半
        b=new PriorityQueue<>((x, y) -> (y - x));//大顶堆  较小的一半
    }
    
    public void addNum(int num) {
        //不等于时即a.size()>b.size(),把num添加到b中
        if(a.size()!=b.size()){
            a.add(num); 
            b.add(a.poll());//通过堆的特性把小顶堆最小值弹出并添加到b中 此时a.size==b.size
        }else{
            //!!!size相同,把值给a
            //通过把num先给b得到堆顶b的最大值并弹出给a
            b.add(num);
            a.add(b.poll());
        }
    }
    
    public double findMedian() {
        return a.size()!=b.size() ? a.peek():(a.peek()+b.peek())/2.0;
    }
}

在这里插入图片描述

有兴趣的可以研究下优先队列的源码
参考leetcode大神 Krahets
看不懂的或者嫌讲的不好的可以看看k神的讲解,有图!


  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算数据流中位数可以通过Flink的ProcessFunction来实现。 具体实现步骤如下: 1. 将数据流按照大小排序 2. 计算数据流的长度,如果是奇数,则中位数为第 (length+1)/2 个元素;如果是偶数,则中位数为第length/2个元素和第(length/2+1)个元素的平均值。 3. 在ProcessFunction的实现,可以使用状态变量来保存数据流的有序列表,并计算中位数。 以下是一个简单的示例代码: ```java public class MedianFunction extends ProcessFunction<Integer, Double> { private ListState<Integer> values; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); values = getRuntimeContext().getListState(new ListStateDescriptor<Integer>("values", Integer.class)); } @Override public void processElement(Integer value, Context ctx, Collector<Double> out) throws Exception { values.add(value); List<Integer> sortedValues = new ArrayList<>(); for (Integer v : values.get()) { sortedValues.add(v); } Collections.sort(sortedValues); int length = sortedValues.size(); if (length % 2 == 0) { double median = (sortedValues.get(length/2) + sortedValues.get(length/2 - 1)) / 2.0; out.collect(median); } else { double median = sortedValues.get(length/2); out.collect(median); } } } ``` 在上述代码,我们使用了ListState来保存数据流的元素,并在每次处理新元素时重新排序并计算中位数。注意,这只是一个简单的示例,实际应用需要考虑更多的问题,比如数据倾斜、数据丢失等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值