295 寻找数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3) 
findMedian() -> 2
进阶:

如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-median-from-data-stream
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法:

1.每次添加数据时,维护元素的排序顺序。

使用插入排序的版本:

通过572 ms93 MB
class MedianFinder {
    /** initialize your data structure here. */
    private int [] list;
    public int length;
    public MedianFinder() {
        list = new int[10000000];
        length = 0;
    }

    public void addNum(int num) {
        //插入排序
        int i=0;
        while(i < length && list[i] <= num){
            i++;
        }
        //NT????????

        for (int j = length+1; j > i; j--) {
            list[j] = list[j-1];
        }

        list[i]=num;
        length++;
    }

    public double findMedian() {
        //
        int k=0;
        double res;
        if(length%2==0){
            k=length/2-1;
            res = (list[k] + list[k+1])/2.0;
        }else{
            //奇数个
            k=length/2+1-1;
            res = list[k];
        }
        return res;
    }

//    public void print(){
//        for (int i = 0; i < length; i++) {
//            System.out.println(list[i]);
//        }
//    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

2.维护两个堆,一个最大堆,一个最小堆, 1)保持最小堆的元素大于最大堆。2)最大堆的元素个数和最小堆的个数差为1

最大堆放较小的数,最大堆放较大的数

如:1 2 3 4 5  最大堆放 3 1 2 最小堆放 4 5,那么中位数就是(4+3) /2 = 3.5 

下面介绍两个思路流程,一个较复杂一个较为简单。

 大小堆流程1(较复杂):

0.设大堆 堆顶元素为maxe,小堆 堆顶元素为mine,添加的元素为X

1.初始向大堆添加第一个元素(当大小堆的都为时)

2.当大小堆元素数量相等时,如果元素X 大于maxe则将元素添加到小堆,

如果元素X 小于maxe则将元素添加到大堆。

3.当大小堆元素数量不等

 3.1如果大堆数量大于小堆:如果元素X 小于maxe则将大堆的堆顶移到小堆(有一个建堆调整过程),将X添加到大堆,否则将X添加到小堆

 3.2如果大堆数量小于小堆:如果元素X 大于mine则将小堆的堆顶移到大堆(有一个建堆调整过程),将X添加到小堆,否则将X添加到大堆

 

大小堆思路流程2(较简洁):

1.如果大小堆元素数量相同时,将新数据添加到大堆,并将大堆顶数据移动到小堆。这样做的结果就是使得小堆的数量比大堆多一个,并保持大堆的元素始终小于等于小堆元素

2.如果大小堆数量不同(即小堆多一个),将新数据那添加到那小堆,将小堆堆顶数据移动到大堆。这样做使得大小堆数量相同,并保持小堆元素始终大于等于大堆

上三个版本的java代码:

版本1是自己实现的堆排序过程,版本2是使用java优先队列,这两个都是用的流程1,

第三个版本是使用 流程2

版本1.

通过716 ms48.5 MBJava

 class MedianFinder {
     /** initialize your data structure here. */
     private ArrayList<Integer> max_heap_list;
     private ArrayList<Integer> min_heap_list;

     public MedianFinder() {
         max_heap_list = new ArrayList<>();
         min_heap_list = new ArrayList<>();
         max_heap_list.add(0);
         min_heap_list.add(0);
     }

     public void adjust_down(ArrayList<Integer> list, int k, String mode){
         //向下调整
         list.set(0,list.get(k));
         for(int i=k*2; i < list.size(); i=i*2){
             if(i+1<list.size()){
                 if(mode.equals("max") && list.get(i)<list.get(i+1)){
                     i++;
                 }
                 if(mode.equals("min") && list.get(i)>list.get(i+1)){
                     i++;
                 }
                 //取子节点 较为mode 的那个
             }

             if(mode.equals("max") &&list.get(i)<list.get(0)){
                 //已经满足条件则退出
                 break;
             }

             if(mode.equals("min") &&list.get(i)>list.get(0)){
                 //已经满足条件则退出
                 break;
             }
             //将父节点位置替换为子节点较大值位置的值
             list.set(k,list.get(i));
             k=i;
         }
         list.set(k,list.get(0));
     }


     public void build_heap(ArrayList <Integer> list,String mode){
         for(int i=list.size()/2;i>0;i--){
             adjust_down(list,i,mode);
         }
     }

     public int pop(ArrayList <Integer> list,String mode){
         int res = list.get(1);
         list.remove(1);
         build_heap(list,mode);
         return res;
     }

     public void adjust_up(ArrayList<Integer> list,String mode){
         int k=list.size()-1;
         int temp = list.get(k);

         for(int i=k/2;i>0;i=i/2){
             if(mode.equals("max")){
                 if(list.get(i)<temp){
                     list.set(k,list.get(i));
                     k=i;
                 }else{
                     break;
                 }
             }
             if(mode.equals("min")){
                 if(list.get(i)>temp){
                     list.set(k,list.get(i));
                     k=i;
                 }else{
                     break;
                 }
             }
         }
         list.set(k,temp);
     }
     public void add_max(int num){
         max_heap_list.add(num);
         adjust_up(max_heap_list,"max");
     }

     public void add_min(int num){
         min_heap_list.add(num);
         adjust_up(min_heap_list,"min");
     }


     public void addNum(int num) {
         //维护两个堆

         if(max_heap_list.size()==1){
             max_heap_list.add(num);
             return;
         }
//        if(min_heap_list.size()==1){
//            min_heap_list.add(num);
//            return;
//        }

//        int max_head_elem = max_heap_list.get(1);
//        int min_head_elem = min_heap_list.get(1);

         if(max_heap_list.size()==min_heap_list.size()){
             int max_head_elem = max_heap_list.get(1);
             if(num<max_head_elem){
                 add_max(num);
             }else{
                 add_min(num);
             }

         }else if(max_heap_list.size()>min_heap_list.size()){
             int max_head_elem = max_heap_list.get(1);
             if(num<max_head_elem){
                 pop(max_heap_list,"max");
                 add_min(max_head_elem);
                 add_max(num);
             }else{
                 add_min(num);
             }

         }else{
             int min_head_elem = min_heap_list.get(1);
             if(num>min_head_elem){
                 pop(min_heap_list,"min");
                 add_max(min_head_elem);
                 add_min(num);
             }else{
                 add_max(num);
             }
         }

     }

     public double findMedian() {
         //
         double res = 0;
         if(max_heap_list.size()==min_heap_list.size()){
             res = (max_heap_list.get(1)+min_heap_list.get(1))/2.0;
         }else if(max_heap_list.size()>min_heap_list.size()){
             res = max_heap_list.get(1);
         }else{
             res = min_heap_list.get(1);
         }

         return res;
     }

     public void print(){
         System.out.println(max_heap_list.toString());
         System.out.println(min_heap_list.toString());
     }
 }
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

  版本2

通过66 ms49.9 MBJava

 class MedianFinder {

     /** initialize your data structure here. */
     private PriorityQueue<Integer> min_heap = new PriorityQueue<>();
     private PriorityQueue<Integer> max_heap = new PriorityQueue<>(new Comparator<Integer>() {
         @Override
         public int compare(Integer o1, Integer o2) {
             return o2-o1;
         }
     });

     public MedianFinder() {

     }

     public void addNum(int num) {
         if(max_heap.isEmpty()){
             max_heap.add(num);
             return;
         }
         if(min_heap.size()==max_heap.size()){
             if(max_heap.peek()<num){
                 min_heap.add(num);
             }else{
                 max_heap.add(num);
             }
         }else if(min_heap.size()<max_heap.size()){
             if(max_heap.peek()>num){
                 int max_e = max_heap.poll();
                 max_heap.add(num);
                 min_heap.add(max_e);
             }else{
                 min_heap.add(num);
             }

         }else {
             if(num>min_heap.peek()){
                 int min_e = min_heap.poll();
                 min_heap.add(num);
                 max_heap.add(min_e);
             }else{
                 max_heap.add(num);
             }
         }

     }

     public double findMedian() {
         if(max_heap.size()==min_heap.size()){
             return (max_heap.peek()+min_heap.peek())/2.0;
         }else if (max_heap.size()>min_heap.size()){
             return max_heap.peek();
         }else {
             return min_heap.peek();
         }
     }
     void print(){
         System.out.println(max_heap.toString());
         System.out.println(min_heap.toString());
     }
 }

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

版本3

通过65 ms49.7 MBJava

 class MedianFinder {

     /** initialize your data structure here. */
     private PriorityQueue<Integer> min_heap = new PriorityQueue<>();
     private PriorityQueue<Integer> max_heap = new PriorityQueue<>(new Comparator<Integer>() {
         @Override
         public int compare(Integer o1, Integer o2) {
             return o2-o1;
         }
     });

     public MedianFinder() {

     }

     public void addNum(int num) {
         //思路:两个堆大小相同时,往大堆里添加,并将头移动到小堆,使得小堆比大堆多一个。
        //不相同时(小堆比大堆多一个),往小堆添加新值,并将小堆的头移到大堆,保持平衡。
         if(max_heap.size()==min_heap.size()){
             max_heap.add(num);
             min_heap.add(max_heap.poll());
         }else{
             min_heap.add(num);
             max_heap.add(min_heap.poll());
         }
     }

     public double findMedian() {
         if(max_heap.size()==min_heap.size()){
             return (max_heap.peek()+min_heap.peek())/2.0;
         }else if (max_heap.size()>min_heap.size()){
             return max_heap.peek();
         }else {
             return min_heap.peek();
         }
     }
     void print(){
         System.out.println(max_heap.toString());
         System.out.println(min_heap.toString());
     }
 }

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

结语:

可以明显的发现思路二的方法比普通排序的方法耗时较少,除了我自己写的方法外(ps:可能自己写的实现效率不高吧。。)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值