剑指offer--数据流中的中位数(大小根堆优先队列)

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

 

解题思路:

最朴素的想法是,通过设定一个ArrayList链表,在插入函数中,每次通过add函数添加num到当前状态下的链表中。

在GetMedian函数中,由于数据是随着数据流不断更新的,所以需要先使用Collections类对链表集合进行排序,之后根据当前数据的数量的奇偶性判断输出结果。算是暴力解法,一定要注意的是在java中,使用位运算作为判定整型变量奇偶性时,需要给位运算的过程添加括号,不然编译会报错,这一点和C++不同。代码如下:

import java.util.ArrayList ;
import java.util.Collections ;
public class Solution {
    public ArrayList<Integer> ans = new ArrayList<>() ;
    public void Insert(Integer num) {
        ans.add(num) ;
    }
 
    public Double GetMedian() {
        double temp = 0.0 ;
        Collections.sort(ans) ;
        int len = ans.size() ;
        if((len & 1) == 0) {
            temp = (ans.get(len/2) + ans.get(len/2-1)) / 2.0 ;
        } else {
            temp = Double.valueOf(ans.get(len/2)) ;
        }
        return temp ;
    }
}

重点来了,另外一种不朴素的做法,大根堆和小根堆做法,即使用优先队列维护数据流组成的数据集合的前后两个堆,作为中位数,前面维护的是一个大根堆,即中位数大于前面集合的所有数值,后面维护的是一个小根堆,即中位数小于后面集合的所有数值。

1.那么再考虑一下细节:我们设定一个计数的变量cur,当一个数据到来时,cur ++。之后判断数据集合的长度是偶数还是奇数。

如果为奇数,那么使用大根堆来存值,故遇到奇数时查询中位数,直接弹出大根堆的堆顶元素即中位数,否则弹出大小根堆堆顶元素取平均数即可。

 

2.那么再考虑一下,当奇数的元素进入大根堆之前,如果该元素大于右边的小根堆元素,那么整个数据的集合就不能保证顺序排列了,结果不是我们期望的。

因此在进入大根堆之前需要先将其放入右边小根堆,之后从小根堆弹出最小的值放入大根堆,同理在偶数的数据到来时,先进入大根堆,之后弹出最大值,放入小根堆即可。

还有一个点需要注意:优先队列默认定义的是小根堆。因此需要自定义实现一下Comparator类接口,并覆写compare函数的排序规则,以使得队列降序输出,每次获得队列中的最大元素。

代码如下:

 

import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.PriorityQueue ;
public class Solution {
    //public ArrayList<Integer> ans = new ArrayList<>() ;
    public PriorityQueue<Integer> min = new PriorityQueue<>() ;
    public PriorityQueue<Integer> max = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1 ;
        }
    }) ;
    public int cur = 0 ;
    public void Insert(Integer num) {
        ++ cur ;
        if((cur & 1) == 1) {
            min.offer(num) ;
            int temp = min.poll() ;
            max.offer(temp) ;
        } else {
            max.offer(num) ;
            int temp = max.poll() ;
            min.offer(temp) ;
        }
        return ;
    }

    public Double GetMedian() {
        double ans = 0.0 ;
        if((cur & 1) == 1) {
            ans = Double.valueOf(max.peek()) ;
        } else {
            ans = (min.peek() + max.peek()) / 2.0 ;
        }
        return ans ;
    }


}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值