【剑指offer】面试题41:数据流的中位数

题目

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

思路

这个题目跟一般的固定数目的数组查找中位数不一样,它是数据流
由于数据是从数据流读出来的,所以数据的数量会随着时间变化而增加。

public class Solution {
    public void insert(Integer num) {
        // TODO
    }
    public Double getMedian() {
        // TODO
    }
}

我们要做的就是实现insertgetMedian这两个函数

这道题会有很多种思路,这里就不一一细说了,把所有解法的时间复杂度列出来:

数据结构插入的时间复杂度得到中位数的时间复杂度
没有排序的数组O(1)O(n)
排序的数组O(n)O(1)
排序的链表O(n)O(1)
二叉搜索树平均O(logn),最差O(n)平均O(logn),最差O(n)
AVL树O(logn)O(1)
最大堆和最小堆O(logn)O(1)

这里使用最方便的,最大堆+最小堆来做
思路在代码注释里↓↓↓

代码(解法1,优先队列)

严重吐槽,牛客网在优先队列挂了我好多次了,好像不让用这个类!!??有关优先队列的我后来都用红黑树做了(解法2)
/**
 * 题目:
 * 如何得到一个数据流中的中位数?
 * 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
 * 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
 * 
 * 思路:
 * 用 一个最大堆  + 一个最小堆 来解决
 * 如果能够保持 最大堆里的所有元素 < 最小堆里面的所有元素
 * 如果元素总个数为奇数,最大堆元素个数=最小堆元素个数-1,则最小堆的顶部元素为中位数;
 * 如果元素总个数为偶数,最大堆元素个数=最小堆元素个数,最中位数=(最小堆的顶部元素+最大堆的顶部元素)/2
 * 
 * 解法:
 * 1.当插入第偶数个元素时(从第0个开始),将元素插入最大堆后,将最大堆顶部元素放到最小堆里
 * 2.当插入第奇数个元素时,将元素插入最小堆后,将最小堆顶部元素放到最大堆里
 * 
 * 所以当插入奇数个元素时,中位数为最小堆顶部元素;
 * 当插入偶数个元素时,中位数为(最大堆顶部元素+最小堆顶部元素)/ 2
 * 
 * @author peige
 */
public class _41_StreamMedian_01 {

    PriorityQueue<Integer> maxQ = new PriorityQueue<>(Collections.reverseOrder());
    PriorityQueue<Integer> minQ = new PriorityQueue<>();

    public void Insert(Integer num) {
        if(((maxQ.size() + minQ.size()) & 1) == 0) {
            maxQ.offer(num);
            minQ.offer(maxQ.remove());
        }
        else {
            minQ.offer(num);
            maxQ.offer(minQ.remove());
        }
    }

    public Double GetMedian() {
        if(maxQ.size() == 0 && minQ.size() == 0)
            return new Double(0.0);
        if(((maxQ.size() + minQ.size()) & 1) == 0) {
            return (double)(minQ.peek() + maxQ.peek()) / 2;
        }
        else {
            return (double)(minQ.peek());
        }
    }

}

解法2(红黑树)

/**
 * 跟使用优先队列的思路一样,这里就不写了
 * 牛客网优先队列编译不过,我也是醉了。
 *  
 * @author peige
 */
public class _41_StreamMedian_02 {

    TreeSet<Integer> maxQ = new TreeSet<>(Collections.reverseOrder());
    TreeSet<Integer> minQ = new TreeSet<>();

    public void Insert(Integer num) {
        if(((maxQ.size() + minQ.size()) & 1) == 0) {
            maxQ.add(num);
            minQ.add(maxQ.pollFirst());
        }
        else {
            minQ.add(num);
            maxQ.add(minQ.pollFirst());
        }
    }

    public Double GetMedian() {
        if(maxQ.size() == 0 && minQ.size() == 0)
            return new Double(0.0);
        if(((maxQ.size() + minQ.size()) & 1) == 0) {
            return (double)(minQ.first() + maxQ.first()) / 2;
        }
        else {
            return (double)(minQ.first());
        }
    }
}

测试(这里放的红黑树的,解法1的也一样)

public class _41_02_Test {

    public static void main(String[] args) {
        test1();
        test2();
    }

    /**
     * 功能测试
     */
    private static void test1() {
        _41_StreamMedian_02 sm = new _41_StreamMedian_02();
        for(int i = 0; i < 10; ++i) {
            sm.Insert(i);
            System.out.print(sm.GetMedian() + "  ");
        }
        System.out.println();
    }

    /**
     * 极端测试
     * 1.没有元素,要获得中位数
     */
    private static void test2() {
        _41_StreamMedian_02 sm = new _41_StreamMedian_02();
        System.out.println(sm.GetMedian());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值