牛客解题思路:数据流中的中位数 纠错记录

数据流中的中位数


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

这一题最简单的做法就是使用堆,我们只需要两个堆,一个大顶堆,一个小顶堆,按照一定的规则装入数据使这两个堆的数据个数绝对值相差不大于1,那么中位数要么是其中一个堆的堆顶,要么是两个堆顶的平均数,在这里我们假定,如果一定有一个堆的元素更多,那就一定只能是大顶堆的。

/* 大顶堆,存储左半边元素 */
private PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
private PriorityQueue<Integer> right = new PriorityQueue<>();

设置大顶堆的时候使用到了lambda表达式,这里还有一个要求,就是大顶堆的所有元素都应该比小顶堆的元素小,如何保障这一点呢?我们只需要在每次插入完数据后调整两个堆的元素个数即可:

public void Insert(Integer val) {
    if(left.isEmpty() || val < right.peek() ){
        left.add(val);
    }else{
        right.add(val);
    }
    if(left.size() == right.size() + 2){
        right.add(left.poll());
    }
    if(left.size() + 1 == right.size()){
        left.add(right.poll());
    }
}

那么我们到时候可以通过直接判断两个堆的元素个数是否相等来确定中位数:

public Double GetMedian() {
    return left.size() == right.size() ? (left.peek() + right.peek()) / 2.0 : (double)left.peek();
}

这里一定要注意转型,前面的数据除以2.0后自动转型,而后面的数据需要我们强制转型。
但是代码一运行就出现了问题,报了空指针异常,我不知道是不是我没弄清楚…牛客网的代码题是不能看出报错的地方在哪吗??然后我用eclipse重新跑了一次:

public class Test {
 /* 大顶堆,存储左半边元素 */
 private static PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
 /* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
 private static PriorityQueue<Integer> right = new PriorityQueue<>();
 public static void Insert(Integer val) {
     if(left.isEmpty() || val < right.peek()){
         left.add(val);
     }else{
         right.add(val);
     }
     if(left.size() == right.size() + 2){
         right.add(left.poll());
     }
     if(left.size() + 1 == right.size()){
         left.add(right.poll());
     }
 }
 public static Double GetMedian() {
     return left.size() == right.size() ? (left.peek() + right.peek()) / 2.0 : (double)left.peek();
 }
 public static void main(String[] args) {
  Insert(5);
  System.out.println(GetMedian());
  Insert(2);
  System.out.println(GetMedian());
  Insert(3);
  System.out.println(GetMedian());
  Insert(4);
  System.out.println(GetMedian());
  Insert(1);
  System.out.println(GetMedian());
  Insert(6);
  System.out.println(GetMedian());
  Insert(7);
  System.out.println(GetMedian());
  Insert(0);
  System.out.println(GetMedian());
  Insert(8);
  System.out.println(GetMedian()); 
 }  
}

然后我发现在 if(left.isEmpty() || val < right.peek()) 判断的时候出现的空指针异常,因为right堆可能是没有元素的,所以我将判断条件改成了:

if(left.isEmpty() || !right.isEmpty() && val < right.peek())

遗憾的是结果又不对,我发现在插入第三个数据的时候出现了问题,然后在草稿纸上模拟了一下这个过程,发现问题了,我在这个判断中漏掉了一个可能:

if(left.isEmpty() || !right.isEmpty() && val < right.peek() || right.isEmpty())

也就是说right为空的时候是不可以随便加数据的,right中的数据一定是left那边“不要了”的数据(●ˇ∀ˇ●)。
由此我们可以想到另一种方式:

private PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
private PriorityQueue<Integer> right = new PriorityQueue<>();
/* 当前数据流读入的元素个数 */
private int N = 0;

public void Insert(Integer val) {
    /* 插入要保证两个堆存于平衡状态 */
    if (N % 2 == 0) {
        /* N 为偶数的情况下插入到右半边。
         * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大,
         * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 */
        left.add(val);
        right.add(left.poll());
    } else {
        right.add(val);
        left.add(right.poll());
    }
    N++;
}

public Double GetMedian() {
    if (N % 2 == 0)
        return (left.peek() + right.peek()) / 2.0;
    else
        return (double) right.peek();
}

其中,N 为偶数的情况下插入到右半边。 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大,因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 。此时right中的元素个数是永远大于等left的,和上一种方式相反。
不过我更喜欢使用最后一种更简单的情况,不断往一个堆中放元素,两个堆中元素个数不一致再移动元素,这样能保证两个堆中的元素个数之差最大不会超过1

PriorityQueue<Integer> min;
PriorityQueue<Integer> max;
/** initialize your data structure here. */
public MedianFinder() {
    min = new PriorityQueue<>();
    max = new PriorityQueue<>((a,b) -> {return b-a;});
}
public void addNum(int num) {
    max.add(num);
    min.add(max.remove());
    if(min.size()>max.size()) max.add(min.remove());
}
public double findMedian() {
    if(max.size() == min.size()) return (max.peek()+min.peek())/2.0;
    else return max.peek();
}

第二次写:

class MedianFinder {
    PriorityQueue<Integer> min;
    PriorityQueue<Integer> max;
    /** initialize your data structure here. */
    public MedianFinder() {
        min = new PriorityQueue<>((o1,o2)->(o2-o1));
        max = new PriorityQueue<>();
    }
    public void addNum(int num) {
        min.add(num);
        max.add(min.poll());
        if(min.size() < max.size()){
            min.add(max.poll());
        }
    }
    public double findMedian() {
        if(max.size() == min.size()){
            return (double)(max.peek() + min.peek())/2;//(double)(max.peek() + min.peek())>>1;
        }else{
            return min.peek();
        }
    }
}

注意这里(double)(max.peek() + min.peek())>>1;是错误的!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值