题目描述:
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
样例:
输入:1, 2, 3, 4
输出:1,1.5,2,2.5
解释:每当数据流读入一个数据,就进行一次判断并输出当前的中位数。
分析:
- 第一种方法:利用未排序的数组存储添加的数据,即每次添加不需要使数组内的元素有序,则插入的时间复杂度为O(1),每次寻找中位数的时间复杂度为O(n),利用快速排序的思想寻找数组中第n/2大的元素,每次将小于该元素的数据放在左侧,大于该元素的数据放在右侧。
- 第二种方法:利用排序的数组存储数据,即每次添加的数据要放在数组中合适的位置,使得数组的元素按序排列,因此添加数据的时间复杂度是O(n),而查找中位数的时间复杂度是O(1)。
- 第三种方法:利用排序的链表存储数据,添加数据到链表中合适的位置其时间复杂度是O(n)。定义两个指针指向链表中的中间节点(如果链表中节点数目是奇数,则这两个指针指向同一个节点),那么可以在O(1)的时间内得出中位数。
- 第四种方法是建立二叉搜索树可以使插入的时间复杂度变为O(logn),但是当二叉搜索树极度不平衡时时间复杂度仍为O(n)。为了得到中位数,可以在二叉树的表示中添加成员变量size表示二叉树中节点的数目,那么查找中位数的时间复杂度为O(logn),最差情况仍需要O(n)。
- 第五种方法是建立平衡的二叉树:插入元素的时间复杂度为O(logn),获取中位数的时间复杂度为O(1)。因为大多数库函数都没有实现该数据结构,所有考虑用堆处理即第六种方法。
- 第六种方法是基于第五种方法的思想维护两个堆即最大堆和最小堆,使得最小堆的堆顶元素大于最大堆的堆顶元素,并确保两个堆内的元素数目不能相差大于1,向堆内添加元素的时间复杂度是O(logn),取出中位数的时间复杂度是O(1)。
//执行用时 : 257 ms, 在Find Median from Data Stream的Java提交中击败了70.92% 的用户
//内存消耗 : 67.7 MB, 在Find Median from Data Stream的Java提交中击败了67.73% 的用户
PriorityQueue<Integer> maxheap;
PriorityQueue<Integer> minheap;
/** initialize your data structure here. */
public MedianFinder() {
maxheap=new PriorityQueue<>((a1, a2) -> a2 - a1);
minheap=new PriorityQueue<>();
}
public void addNum(int num) {
if(maxheap.isEmpty()) {
maxheap.offer(num);
return;
}
if(num>maxheap.peek()) {
minheap.offer(num);
}else {
maxheap.offer(num);
}
if(maxheap.size()-minheap.size()>1)
minheap.offer(maxheap.poll());
else if(minheap.size()-maxheap.size()>1)
maxheap.offer(minheap.poll());
}
public double findMedian() {
double num=0;
int maxlen=maxheap.size();
int minlen=minheap.size();
if(maxlen==minlen) {
if(maxlen==0)
return num;
num=(maxheap.peek()+minheap.peek())/2.0;
return num;
}
if(maxlen>minlen)
return maxheap.peek();
if(minlen>maxlen)
return minheap.peek();
return num;
}