题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
示例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...
题目分析
在存储数据的结构中,因为要能获得中位数,需要保持数据的有序性,同时方便直接访问中间数据,可以使用 arrayList 或 linkedList,前者查询速度快,后者插入速度快;
同时需要考虑一个问题,是在插入的时候就保持数据的有序性,还是在获取的时候才对数据排序。
这里可以根据具体的需求进行选择,若是插入操作很多,读取操作少的,可以选择在读取的时候才排序;若是插入操作少而读取操作多的,可以选择在插入时即保证有序。
解题思路
方法1:在获取时,对数据排序
插入的时候,可以对数据不进行排序,仍按照插入顺序插入;
但在获取中位数时,需要对数据排序,然后再进行取中间值;
排序的方法有:快速排序(nlogn),堆排序(nlogn),归并排序(nlogn)等,可以选择任意一种。
方法2:在插入时,对数据排序
如果想要在插入的时候就进行排序,可以在插入目标值的时候,使用二分法O(logn)进行插入位置的查找,同时考虑到使用的存储数据结构,若是arrayList则还需要O(n)的时间进行数据的移动,若是linkedList则只需要O(1)插入数据,不需要移动数据;
而对于获取数据来说,若使用arrayList,只需要O(1)的时间;使用linkedList需要O(n)的时间,所以可以以针对不同的需求和使用场景进行选择。
插入数据的过程如下:
代码实现
方法1:在获取时,对数据排序
// 使用ArrayList存储数据 ArrayList<Integer> store = new ArrayList<Integer>(); public void Insert(Integer num) { // 插入时不操作,直接插入 store.add(num); } public Double GetMedian() { // 在获取中位数时 int length = store.size(); if(length == 0) return 0.0; if(length == 1) return store.get(0)/1.0; // 先对数据排序 Collections.sort(store); // 再根据数据个数的奇偶性获取中位数 int index = length/2; if(length%2 == 1){ return store.get(index)/1.0; }else{ return (store.get(index)+store.get(index-1))/2.0; } }
时间复杂度:插入方法只要直接在数据末尾添加,时间复杂度为O(1);读取方法,每次都要重新对数据排序,使用的是快速排序方法,时间复杂度O(n*logn);
空间复杂度:O(n),需要使用额外的空间来存储数据,空间使用n;
方法2:在插入时,对数据排序
ArrayList<Integer> res = new ArrayList<>(); public void Insert(Integer num) { // 定义加入的下标位置 int index = -1; if(!res.isEmpty()){ // 不为空时,初始化为数组末尾 index = res.size()-1; } // 进行插入排序,找到目标数字正确的插入位置 while(index>=0 && num<res.get(index)){ index--; } // index位置上的是刚比num小的数,插到它的后面 res.add(index+1, num); } public Double GetMedian() { // 获取链表的中间结点位置 int mid = res.size()/2; Double median = 0.0; if(res.size()%2==0){ // 若是偶数,则是中间两个数的平均值 median = Double.valueOf((res.get(mid)+res.get(mid-1))/2.0); }else{ // 若是奇数,则是中间的数 median = Double.valueOf(res.get(mid)); } return median; }
时间复杂度:插入方法需要对数据进行大小比较,得到目标数据的插入位置,最差需要遍历前面的所有数据,时间复杂度为O(n);读取方法,只需要直接获取中间的数据,时间复杂度O(1);
空间复杂度:O(n),需要使用额外的空间来存储数据,空间使用n。