中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[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 范围内,你将如何优化你的算法?思路:
比较简单,直接看代码即可。比较慢,不推荐。
代码:
class MedianFinder { List<Integer> res = new ArrayList<>(); public void addNum(int num) { res.add(num); } public double findMedian() { int n = res.size(); Collections.sort(res); return n%2==1 ? res.get(n/2) : (res.get(n/2-1)+res.get(n/2))/2.0; } }时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) 空间复杂度: O ( n ) O(n) O(n)
思路:
将数加入一个有序数组,然后返回中间位置。具体请结合注释理解。比较慢,不推荐。
代码:
class MedianFinder { List<Integer> res = new ArrayList<>(); public void addNum(int num) { // 二分查找 O(logn) ... // 挪动位置,然后插入 O(n) ... res.add(num); } public double findMedian() { int n = res.size(); return n%2==1 ? res.get(n/2) : (res.get(n/2-1)+res.get(n/2))/2.0; } }时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n)
思路: (主要来自参考2)
1、中位数位置特点
对数据流排序后,中位数位于这些数中间1个(奇数个)或两个数取平均(偶数个)。
2、中位数值特点
中位数只位于前有序数组的最大值(奇数个),或者是**(前有序数组的最大值+后有序数组的最小值)/ 2**。
即我们只关心两个有序数组的最值,所以可以用两个优先队列来解决。前面只关心最大值,用大顶堆,后面只关心最小值,用小顶堆。 3、两个堆性质
1)大顶堆堆顶元素 <= 小顶堆元素; 2)大顶堆元素个数与小顶堆元素相等,或者多1.
说明:maxHeap.size() +1 == minHeap.size() X 即大顶堆元素个数比小顶堆元素个数少1的情况不可能出现,因为后有序数组元素个数不会多余前有序数组元素个数4、数量控制
1)maxHeap.size() == minHeap.size() +1
为让大顶堆元素多1个,可采用流程:大顶堆 --> 小顶堆 --> 大顶堆2)maxHeap.size() == minHeap.size()
在前一步,大顶堆元素多一个,现在添加1个元素,为了使得两者相等,必须要有一个元素移入小顶堆,即必须具有流程:大顶堆 --> 小顶堆。小结: 一定要有 大顶堆–>小顶堆 的流程,为了保证大顶堆的元素个数 >= 小顶堆的元素个数,后面再根据情况决定是否加上 小顶堆–>大顶堆 的流程。
代码:
PriorityQueue<Integer> minHeap = new PriorityQueue<>();//heap is a minimal heap by default PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());//change to a maximum heap // Other maxHeap define method // PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> (b - a)); // Adds a number into the data structure. public void addNum(int num) { maxHeap.offer(num); minHeap.offer(maxHeap.poll()); if (maxHeap.size() < minHeap.size()) maxHeap.offer(minHeap.poll()); } // Returns the median of current data stream public double findMedian() { if (maxHeap.size() == minHeap.size()) return (maxHeap.peek() + minHeap.peek()) / 2.0; else return maxHeap.peek(); }时间复杂度: O ( l o g n ) O(logn) O(logn),插入 O ( l o g n ) O(logn) O(logn),查询 O ( 1 ) O(1) O(1) 空间复杂度: O ( n ) O(n) O(n)
思路:
用二分搜索树来解决上述问题。这里的TreeNode声明加了一个size字段。其他的主要还是递归的方法,理解不算难。
代码-递归版:
class MedianFinder { private class TreeNode { int val; int size; TreeNode left; TreeNode right; TreeNode(int val) { this.val = val; this.size = 1; } } private TreeNode root; /** initialize your data structure here. */ public MedianFinder() { } public void addNum(int num) { if (this.root == null) { root = new TreeNode(num); } else { this.addNum(num, this.root); } } private void addNum(int num, TreeNode node) { if (node.val >= num) { if (node.left == null) { node.left = new TreeNode(num); } else { addNum(num, node.left); } } else { if (node.right == null) { node.right = new TreeNode(num); } else { addNum(num, node.right); } } node.size++; } public double findMedian() { if (this.root.size % 2 == 1) { return search(this.root.size / 2 + 1, this.root); } else { int left = search(this.root.size / 2, this.root); int right = search(this.root.size / 2 + 1, this.root); return (left + right) / 2.0; } } public int search(int index, TreeNode node) { if (node.size == 1) { return node.val; } int leftSize = node.left != null ? node.left.size : 0; if (leftSize >= index) { return search(index, node.left); } else if (leftSize + 1 == index) { return node.val; } else { return search(index - leftSize - 1, node.right); } } }思路: 类似上面,只不过用了迭代的方式。
代码-迭代版:
class MedianFinder { class TreeNode{ int val; TreeNode parent,left,right; TreeNode(int val, TreeNode p){ this.val=val; this.parent=p; left=null; right=null; } void add(int num){ if(num>=val){ if(right==null) right=new TreeNode(num,this); else right.add(num); }else{ if(left==null) left=new TreeNode(num,this); else left.add(num); } } TreeNode next(){ TreeNode ret; if(right!=null){ ret=right; while(ret.left!=null) ret=ret.left; }else{ ret=this; while(ret.parent.right==ret) ret=ret.parent; ret=ret.parent; } return ret; } TreeNode prev(){ TreeNode ret; if(left!=null){ ret=left; while(ret.right!=null) ret=ret.right; }else{ ret=this; while(ret.parent.left==ret) ret=ret.parent; ret=ret.parent; } return ret; } } int n; TreeNode root, curr; // Adds a number into the data structure. public void addNum(int num) { if(root==null){ root = new TreeNode(num,null); curr=root; n=1; }else{ root.add(num); n++; if (n%2==1) { if(curr.val<=num) curr = curr.next(); } else if(curr.val>num) curr = curr.prev(); } } // Returns the median of current data stream public double findMedian() { if(n%2==0){ return ((double)curr.next().val+curr.val)/2; }else return curr.val; } };时间复杂度: O ( l o g n ) O(logn) O(logn) 空间复杂度: O ( n ) O(n) O(n)
1、数据流的中位数 2、优先队列(Python 代码、Java 代码) 3、Short simple Java/C++/Python, O(log n) + O(1) 4、JAVA-----------Easy Version To Understand!!! 5、18ms beats 100% Java Solution with BST 6、22ms Java solution using binary tree, beats 99.82% of submissions 7、啥也不说了咱们来一个手撕索引重复红黑树吧。。。。。。。。。我哭了 8、Solutions to follow-ups 9、[Java] Simple Code - Follow Up