剑指offer面试题41(java版):数据流中的中位数

welcome to my blog

剑指offer面试题41(java版):数据流中的中位数

题目描述

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

思路

  • 使用最大堆和最小堆实现在O(1)时间返回中位数. 插入数据的时间复杂度是O(n)
  • 首先要保证数据平均分配到两个堆中, 因此两个堆中的元素的个数相差最多为1
  • 为实现平均分配, 可以在数据的总数目是偶数时,把新数据插入最小堆; 在数据的总数目是奇数时,把新数据插入最大堆
  • 约束条件:要始终保证最小堆的堆顶元素比最大堆的堆顶元素大
  • 如果数据的总数目是偶数时, 新数据应该插入最小堆, 但是如果新数据比最大堆的堆顶元素小, 也就是新数据插入最小堆会破坏约束条件, 此时可以将新数据插入最大堆, 然后再将最大堆的堆顶元素插入最小堆
  • 如果数据的总数目是奇数时, 新数据应该插入最大堆, 但是如果该元素比最小堆的堆顶元素大, 也就是新数据插入最大堆会破坏约束条件, 此时可以将新数据插入最小堆, 然后再把最小堆的堆顶元素插入最大堆

笔记

  • 判断奇偶用&1操作
  • 注意体会注释

复杂度

  • 时间复杂度: 往最大/小堆中插入元素的时间复杂度是O(logn); 返回堆顶元素的复杂度为O(1). 总的时间复杂度是O(logn)
第三次做; Comparator的写法熟悉了一些; 开始忘记给PriorityQueue加泛型了
class MedianFinder {
    PriorityQueue<Integer> heapS;
    PriorityQueue<Integer> heapL;
    /** initialize your data structure here. */
    public MedianFinder() {
        heapS = new PriorityQueue<>();
        heapL = new PriorityQueue<>(new Comparator<Integer>(){
            public int compare(Integer o1, Integer o2){
                return o2 - o1;
            }
        });
    }
    
    public void addNum(int num) {
        //当heapS为空或者heapS和heapL的size一样时, 优先向heapS中添加元素
        if(heapS.isEmpty() || heapS.size()==heapL.size()){
            if(!heapL.isEmpty() && num < heapL.peek()){
                heapL.add(num);
                num = heapL.poll();
            }
            heapS.add(num);
        }else{
            if(!heapS.isEmpty() && num > heapS.peek()){
                heapS.add(num);
                num = heapS.poll();
            }
            heapL.add(num);
        }
    }
    
    public double findMedian() {
        return heapS.size()>heapL.size()? heapS.peek() : (heapL.peek() + heapS.peek())/2.0;
    }
}
import java.util.PriorityQueue;
import java.util.Comparator;

public class Solution {
    public PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>(){
        public int compare(Integer o1, Integer o2){
            return o2 - o1;
        }
    });
    public PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    public int flag = 1;
    public void Insert(Integer num) {
        //先往大根堆中插入元素
        if(flag==1){
            if(!minHeap.isEmpty() && num > minHeap.peek()){
                minHeap.add(num);
                num  = minHeap.poll();
            }
            maxHeap.add(num);
            flag = 1 - flag;
        }
        //往小根堆汇总插入元素
        else{
            if(!maxHeap.isEmpty() && num < maxHeap.peek()){
                maxHeap.add(num);
                num = maxHeap.poll();
            }
            minHeap.add(num);
            flag = 1 - flag;
        }
    }

    public Double GetMedian() {
        //因为是先往大根堆中插入数据, 所以一共有奇数个数据的话, 大根堆中的数据个数比小根堆多1
        return maxHeap.size() > minHeap.size() ? 1.0*maxHeap.peek() : (maxHeap.peek() + minHeap.peek())/2.0;
    }
}
第二次做, 使用大根堆和小根堆, 核心: 保持大根堆小根堆size差距不超过1的情况下, 正确地插入数据: 要保证大根堆的最大值小于小根堆的最小值; 堆为空就不涉及比较过程了,直接插入
  • public int compare(Integer o1, Integer o2){}
  • Double.valueOf()
import java.util.PriorityQueue;
import java.util.Comparator;

public class Solution {
    //大根堆
    public PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(
        new Comparator<Integer>(){
            public int compare(Integer o1, Integer o2){
                return o2 - o1;
            }
        }
    );
    //小根堆
    public PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    public int count=1;
    //要保证大根堆的最大值小于小根堆的最小值
    //插入策略: 先插入再调整; 轮流向大根堆和小根堆中插入一个数, 不妨先大根堆,再小根堆. 这样就能保证大根堆和小根堆的size最多差1
    public void Insert(Integer num) {
        //第奇数个数插入大根堆
        if(count%2==1){
            //当前数比小根堆的最小值大的话,就不能插入大根堆了,只能插入小根堆
            if(!minHeap.isEmpty() && num > minHeap.peek()){
                minHeap.add(num);
                num = minHeap.poll();
            }
            maxHeap.add(num);
            count++;
        }
        //第偶数个数插入小根堆
        else{
            //如果当前数小于大根堆的最大值,就只能插入大根堆了
            if(!maxHeap.isEmpty() && num < maxHeap.peek()){
                maxHeap.add(num);
                num = maxHeap.poll();
            }
            minHeap.add(num);
            count++;
        }
    }

    public Double GetMedian() {
        return maxHeap.size()==minHeap.size() ? (maxHeap.peek()+minHeap.peek())/2.0 : maxHeap.peek()*1.0;
    }
}
第二次做, 使用了ArrayList和Arrays实现, 这题貌似要练习一下堆的使用
import java.util.ArrayList;
import java.util.Arrays;

public class Solution {
    ArrayList<Integer> al = new ArrayList<>();
    public void Insert(Integer num) {
        al.add(num);
    }

    public Double GetMedian() {
        int N = al.size();
        int[] arr = new int[N];
        for(int i=0; i<N; i++)
            arr[i] = al.get(i);
        Arrays.sort(arr);
        return (N%2)==1 ? Double.valueOf(arr[N/2]) : Double.valueOf((arr[N/2-1]+arr[N/2])/2.0);
    }
}
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
    /*
        最大堆和最小堆通过java的优先队列实现
        */
    PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(10, new Comparator<Integer>(){ //使用匿名函数实现Comparator
        public int compare(Integer o1, Integer o2) {
            //PriorityQueue默认是小顶堆,实现大顶堆,需要反转默认排序器
            return o2.compareTo(o1);
        }
    });
    int count = 0;
    public void Insert(Integer num) {
        count++;
        if((count & 1) == 1){ //第奇数个数, 不妨插入最大堆
            if(!minHeap.isEmpty() && num>minHeap.peek() ){//当前值比minHead的最小值要大,不能插入最大堆
                minHeap.offer(num);
                num =  minHeap.poll();
            }
            maxHeap.offer(num);
        }
        else{ // 第偶数个数, 不妨插入最小堆
            if(!maxHeap.isEmpty() && num<maxHeap.peek()){// 当前值比maxHeap的最大值还小,所以不能直接插入最小堆
                maxHeap.offer(num);
                num = maxHeap.poll();
            }
            minHeap.offer(num);
        }
    }

    public Double GetMedian() {
        if((count & 1) == 0)
            return (minHeap.peek() + maxHeap.peek())/2.0;
        else
            return maxHeap.peek()*1.0;
    }
}
牛客上一个大佬手写了AVL平衡二叉树
  • 有机会掌握一下
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值