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平衡二叉树
- 有机会掌握一下