数据流中的中位数

题目链接:https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey.

插入排序法

基本思路

要求数据流中的中位数,每输入一个数,加入到原来的数据流中后,要找到新的数据流的中位数。因此我们可以利用插入排序的方法。每次都在原先有序的数据流中插入新来的数据,形成新的有序数据流。并根据新的数据流的长度,可以在o(1)时间内定位到中位数。

代码

class Solution {
public:
  	#define SCD static_cast<double>//强制转换
    void Insert(int num) {
        if(res.empty()) res.push_back(num);
        else{//插入排序 
        	vector<int>::iterator pos=lower_bound(res.begin(),res.end(),num);//在原始有序序列中找到第一个大于等于num的位置 
			res.insert(pos,num);
		}
			
    }

    double GetMedian() { 
   		int len =res.size();
        if(len % 2 == 1) { // 长度为奇数
            return SCD(res[len >> 1]);
        }else{
            return SCD((res[len >> 1]+res[(len - 1) >> 1])) / 2;
        }
    }
private:
	vector<int> res; 
};

基本思想

使用堆来解决问题,我们可以构造两个堆,一个大顶堆,放小于中位数的数据;一个小顶堆,放大于中位数的数据。每次来一个新的数,我们要维护堆的两个方面:

  1. 新来的数,应该放在哪个堆:由于我们希望通过左边的大顶堆和右边的小顶堆来直接确定中位数,所以我们要保证两个堆中元素的数量要一样或者只相差一个元素。因此,我们可以这样设置,
    (1)当两个堆的元素的数量一样时,我们把新的数放在右边的小顶堆。
    (2)当左边的堆的元素数量比右边的堆的元素数量少时,我们把新的数放在右边的大顶堆中。
  2. 第二个问题是,我们要维护两个堆。比如第一步中的(1),我们将新的数放在右边小顶堆前,要判断新的数是否比左边的大顶堆的顶要小,
    (a) 如果小于左边大顶堆的顶,根据我们之前的约定,左边大顶堆的所有元素要比右边小顶堆的元素小,我们需要将新元素和左边大顶堆的顶交换,即num=max[0],同时,由于左边的大顶堆的顶已经改变了,所以可能不再满足大顶堆的要求,我们需要将左边的重新调整为大顶堆。接着,将更新的num插入到右边的大顶堆中,并重新调整右边的为大顶堆。
    (b) 如果大于左边的顶,直接将新的数插到右边,并重新调整右边为小顶堆。
    同理,比如第一步中的(2):
    (a) 如果大于右边小顶堆的顶,根据我们之前的约定,左边大顶堆的所有元素要比右边小顶堆的元素小,我们需要将新元素和右边小顶堆的顶交换,即num=min[0],同时,由于右边的小顶堆的顶已经改变了,所以可能不再满足小顶堆的要求,我们需要将右边的重新调整为小顶堆。接着,将更新的num插入到左边的大顶堆中,并重新调整左边的为大顶堆。
    (b) 如果小于右边的顶,直接将新的数插到左边,并重新调整左边为大顶堆。

代码

class Solution {
public:
  	#define SCD static_cast<double>
    vector<int> min;
	vector<int> max;
    	void Insert(int num){
	
		if(((max.size()+min.size())&1)==0){//偶数 
		//	cout<<"Insert"<<max.size()<<"=="<<endl;
			if(max.size()>0&&num<max[0]){// 2.如果t比左边大顶堆的根要小,则把左边大顶堆的根取出来root,将t插入到左边,root插入到右边小顶堆 
				max.push_back(num);//将元素加入大顶堆 
				push_heap(max.begin(),max.end(),less<int>());//加入新元素后,重新调整为大顶堆 
				num=max[0];
				pop_heap(max.begin(),max.end(),less<int>()); //弹出堆顶元素 
				max.pop_back();//将调整后的大顶堆的根弹出来,用于加到小顶堆(右边) //删除最后一个元素
			}
		//	1.如果 t比左边大顶堆的根要大,则t直接插入到右边的小顶堆  面弹出来的根要加入小顶堆的操作一样 
	
			min.push_back(num);
			push_heap(min.begin(),min.end(),greater<int>());
			 
		}
		else{//奇数 ,则可将新输入过来的数t插入到大顶堆:
		//这时会有两种情况:1.如果 t比右边小顶堆的根要小,则t直接插入到左边的大顶堆;
		//2.如果t比右边小顶堆的根要大,则把右边小顶堆的根取出来root,将t插入到右边,root插入到左边边大顶堆 
			 if(min.size()>0&&num>min[0]){
			 	min.push_back(num);
			 	push_heap(min.begin(),min.end(),greater<int>());
			 	num=min[0];
			 	pop_heap(min.begin(),min.end(),greater<int>());
			 	min.pop_back();	//删除最后一个元素 
			 }
			 max.push_back(num);
			 push_heap(max.begin(),max.end(),less<int>());
			
		} 
    }
    double GetMedian(){
        if(min.size()>max.size()) { // 长度为奇数
            return SCD(min[0]);
        }else{
            return SCD((max[0]+min[0])) / 2;
        }
    }
};

优先队列

基本思想

优先队列本身就是一个堆,因此。因此,每来一个新的数,我们只需要判断放在哪个队列就好,优先队列会自动将加入的数据后的数据集,调整为大顶堆或小顶堆。

代码

class Solution {
public:
  	#define SCD static_cast<double>
	priority_queue<int,vector<int>,greater<int> > min;
	priority_queue<int> max;
	//使用优先队列
	void Insert(int num){
		if(min.size()==max.size()){
			if(max.size()>0&&max.top()>num){//num比大顶堆的顶要小,说明num应该要放在左边,原来左边大顶堆的顶要放在右边 
				max.push(num);
				num=max.top();//更新num 
				max.pop();//将大顶堆的顶弹出来 
			}
			min.push(num);
		}
		else{
			if(min.size()>0&&min.top()<num){//num比小顶堆的顶要大,说明num应该放在右边,原来有边小顶堆的顶要放在左边 
				min.push(num);//先入左边的优先队列 
				num=min.top();//将小顶堆的顶更新到num 
				min.pop();//将小顶堆的顶弹出 
			}
			max.push(num);//
		}
	} 
	double GetMedian(){
        if(min.size()>max.size()) { // 长度为奇数
            return SCD(min.top());
        }
		else{
            return SCD((min.top()+max.top())) / 2;
        }
	}
};

总结

刚开始拿到题目,没有理解清,题目的要求。原来数据流是要求一个一个输入,然后再一个一个判断中位数。一开始,我想着的用一个优先队列来保存全部数据,但是我发现,排序后的优先队列,我们不能在O(1)的时间内马上定位到中位数,所以还是需要两个优先队列。编程路上,砥砺前行~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值