《剑指Offer》Java刷题 NO.63 数据流中的中位数(数据流、最大堆、最小堆、中位数、PriorityQueue)

《剑指Offer》Java刷题 NO.63 数据流中的中位数(数据流、最大堆、最小堆、中位数、PriorityQueue)

传送门:《剑指Offer刷题总目录》

时间:2020-07-26
题目:

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


知识点:

  1. Java的PriorityQueue 是从JDK1.5开始提供的新的数据结构接口,默认内部是自然排序,比如Integer,就是按照数值大小的升序,所以自然状态下是小顶堆,也可以自定义比较器,完成大顶堆;默认初始化容量是11

  2. 复习一下比较器,以之前写的Student类为例
    compareTo(Object o)默认是升序,也就是return this.age-o.age

  3. 复杂度:
    不能插入null,否则会报空指针异常


思路:
将数据分为[0 … median - 1], [media], [media +1,…, arr.size() - 1]三段,将第一段放进最大堆,第三段放进最小堆,media可能在最小堆,也可能在最大堆;保证最小堆和最大堆元素个数差值不超过1,同时最小堆元素一定都大于最大堆元素,所以要:

  • 新来的数分第奇数个和第偶数个交叉插入最大堆和最小堆
  • 过滤:每次插入最大堆时过滤出最大堆的最新堆顶(最大值)放进最小堆,同理,插入最小堆时过滤出最小堆的最新堆顶(最小值)放进最大堆。
  • 可以看出,每次插入新来的数时,插入最大堆实际上是最小堆多了一个值(多了最大堆中的最大值),插入最小堆实际上是最大堆多了一个值(多了最小堆中的最小值),这就是过滤的作用,保证了最小堆元素一定都大于最大堆元素

Java代码:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

/**
 * @author LiMin
 * @Title: GetBuffer
 * @Description: 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的
 * 平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
 * @date 2020/7/26  20:58
 */
public class GetBuffer {
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    //最大堆的建立
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
    private int count = 0;

    public void insert(Integer num) {
        count++;
        if ((count & 1) == 0) {//第偶数个放入最小堆,然后把最小堆的最小值放入最大堆;位运算判断奇偶效率较高
            minHeap.offer(num);
            int min = minHeap.poll();
            maxHeap.offer(min);
        } else {//第奇数个放入最大堆,然后把最大堆的最大值放入最小堆
            maxHeap.offer(num);
            int max = maxHeap.poll();
            minHeap.offer(max);
        }
    }

    public Double getMedian() {
        //注意每次取的时候只是读,不能取出,要用peek函数
        if ((count & 1) == 0) {//偶数个取两个堆顶元素的平均值
            return (double) (minHeap.peek() + maxHeap.peek()) / 2;//注意是先把两个整数的和转化成double再除以2
        } else {//奇数个直接从最小堆取堆顶
            return (double) (minHeap.peek());
        }
    }

    public static void main(String[] args) {
        //测试用例:5 2 3 4 1 6 7 0 8
        Scanner input = new Scanner(new BufferedInputStream(System.in));
        PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
        GetBuffer getBuffer = new GetBuffer();
        String str = input.nextLine();
        Scanner in = new Scanner(str);
        while (in.hasNext()) {//在等待要扫描的输入时,此方法可能阻塞。扫描器将不执行任何输入。所以循环会一直下去。
            getBuffer.insert(in.nextInt());
            out.println(getBuffer.getMedian());
            out.flush();
        }
        in.close();
        out.close();
        return;
    }
}
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页