JZ41 数据流中的中位数

JZ41 数据流中的中位数

描述

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

数据范围:数据流中数个数满足 1 ≤ n ≤ 1000 ,大小满足 1≤ val ≤1000

进阶: 空间复杂度 O(n) , 时间复杂度 O(nlogn)

示例1

输入:

[5,2,3,4,1,6,7,0,8]

返回值:

"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
// 说明:数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...   

示例2

输入:

[1,1,1]

返回值:

"1.00 1.00 1.00 " 

解析

本题的目的就是求一个一直在变化的数组(即读取的数据流)的中位数。求中位数需要先让数组有序,所以肯定少不了排序。可以很快想到的一种解决方法是,每次读取新数据到数组中后,都进行一次排序以保证数组有序,然后按照中位数的定义获取中位数即可。

不过每次读取新数据后都进行排序,时间复杂度取决于排序算法的时间复杂度,且每读进一个数就要执行一次排序,时间复杂度甚至可能达到 O(n^3),写出来也非常膈应。

所以需要寻找另外的方法,以降低时间复杂度。可以注意到,若能一直保持数组有序,则每次都是在有序数组中插入新数据。这样问题就转换成了:在有序数组中插入新数据,同时保持数组有序,应对这个问题的算法想必就很容易想到了:二分查找插入法。

代码清单

import java.util.*;

public class Solution {

    private ArrayList<Integer> list = new ArrayList<>();
    public void Insert(Integer num) {
        int start = 0;
        int end = list.size()-1;
        int mid = 0;
        if(list.isEmpty()){
            list.add(num);
            return;
        }
        // 需要这个 =,当 start 与 end 重合时,再进行一次判断
        // 若当前数比插入数小,则插入的位置应该是 start+1;反之 end-1 结束循环,插入位置就是 start
        while(start <= end){
            mid = start+(end-start)/2;
            if(num < list.get(mid))
                end = mid - 1 ;
            else
                start = mid + 1 ;
        }
        // 是 start 而不是 mid
        list.add(start,num);
    }

    public Double GetMedian() {
        int size = list.size();
        double median;
        if(size % 2 == 1)
            median = (double)list.get(size/2);
        else
            median = (double)(list.get(size/2)+list.get((size/2)-1))/2;
        return median;
    }
}

其中,Insert() 方法就是二分查找插入的方法,维护 list 数组的有序;然后 GetMedian() 方法可以直接按中位数定义获取数组的中位数。

二分查找插入不是什么很难的算法,甚至属于简单算法,不过还是折磨到了我。

写的时候遇到了两个问题:

  1. **最后插入的位置应该是 start 而不是 mid。**因为 mid 是 start 和 end 的平均数,所以遇到除不尽的情况只会靠向 start( int 型取舍)。如在 [1,3,4] 中插入 2,mid 最终会变成 0,即将 2 插入到 1 前面,这显然是大错特错。
  2. **循环条件 start <= end 中的 = 是非常关键的。**当 start 与 end 重合时,此时的 mid 等于 start,会再进行一次判断,若当前数比插入数小,则插入的下标应该是 start+1;反之 start 不变, end-1 结束循环,插入下标就是 start。如在 [1,3,4] 中插入 2,start 与 end 都是 0 时,由于 1 < 2,start 变成 1,最后插入的下标就是 1;又如在在 [3,3,4] 中插入 2,start 一直是 0 ,end 会收缩为 -1 结束循环,所以插入下标就是 0。

时间复杂度为二分查找插入的 O(logn),和挪动数据的 O(n)。简单的算法,也容易犯错。

总结

本题有最简单解法,但我肯定不会这样做。不过二分查找插入的方法也算不上最好,本题的最优解应该是使用大根堆与小根堆,只需使用堆维护中位数,不需要挪动数据,时间复杂度为 O(logn)。不过我开摆了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值