題目及測試
package pid295;
/* 數據流的中位數
中位數是有序列表中間的數。如果列表長度是偶數,中位數則是中間兩個數的平均值。
例如,
[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 範圍內,你將如何優化你的算法?
*/
public class main {
public static void main(String[] args) {
MedianFinder obj = new MedianFinder();
obj.addNum(1);
obj.addNum(2);
double param_2 = obj.findMedian();
System.out.println(param_2);
obj.addNum(1);
param_2 = obj.findMedian();
System.out.println(param_2);
}
}
解法1(成功,88ms,較慢)
內部有一個鏈表,代表一個有序的鏈表,有head和tail的值爲Integer的min和max
內部有一個TreeMap<Integer, List<Node>> treeMap,Integer爲數字,List<Node>爲該數字對應的節點(可能有多個),新加入的在最後面
每次插入時,先插入treeMap裏對應list的最後一個,然後得到對應節點的前一個,插入鏈表中。
然後移動mid1和mid2到應該對應的位置,結果返回mid1和mid2的中間值
速度是O(log n)
package pid295;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
class MedianFinder {
class Node{
int num;
Node next;
Node prev;
public Node(int num){
this.num = num;
}
}
Node head = new Node(Integer.MIN_VALUE);
Node tail = new Node(Integer.MAX_VALUE);
int size = 0;
Node mid1 = head;
Node mid2 = tail;
int mid1Index = -1;
int mid2Index = -1;
TreeMap<Integer, List<Node>> treeMap = new TreeMap<>();
/** initialize your data structure here. */
public MedianFinder() {
head.next = tail;
tail.prev = head;
}
// head 1 2 tail
public void addNum(int num) {
Node now = new Node(num);
List<Node> list = null;
if(!treeMap.containsKey(num)){
list = new ArrayList<>();
}else{
list = treeMap.get(num);
}
list.add(now);
treeMap.put(num, list);
Node prev = null;
if(treeMap.containsKey(num)&&(treeMap.get(num).size()!=1)){
list = treeMap.get(num);
prev = list.get(list.size()-2);
}else if(treeMap.lowerEntry(num)==null){
prev = head;
}else{
list = treeMap.lowerEntry(num).getValue();
prev = list.get(list.size()-1);
}
now.next = prev.next;
prev.next = now;
now.next.prev = now;
now.prev = prev;
size ++;
if(size == 1){
mid1 = now;
mid2 = now;
mid1Index = 0;
mid2Index = 0;
}else{
// 相同的,新加入的放到最後面
if(num<mid1.num){
mid1Index++;
}
if(num<mid2.num){
mid2Index++;
}
// 1 0 0
// 2 0 1
// 3 1 1
int mid1NowIndex = (size -1)/2;
int mid2NowIndex = size/2;
while(mid1Index != mid1NowIndex){
if(mid1Index<mid1NowIndex){
mid1Index++;
mid1 = mid1.next;
}else{
mid1Index--;
mid1 = mid1.prev;
}
}
while(mid2Index != mid2NowIndex){
if(mid2Index<mid2NowIndex){
mid2Index++;
mid2 = mid2.next;
}else{
mid2Index--;
mid2 = mid2.prev;
}
}
}
}
public double findMedian() {
return ((double)mid1.num+(double)mid2.num)/2;
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
解法2(別人的)
我們需要明確,對於此題而言,找到median就意味着,我們可以將此數據流分爲兩部分,即第一部分值全部小於median(or =),第二部分值全部大於median(or =)。
所以我們用maxheap存第一部分,並按照倒序存放;minheap來存第二部分,並按照順序排放。
我們先立下一個約定,即maxheap總比minheap多一個,方便於我們之後進行查找。這樣一來,初始第一個值就加入maxheap。
比較即將加入進heap的數字與兩個堆堆頂的數字,若大於maxheap,則需要放進minheap中。
在後面陸續的增加中,我們只需保持兩個heap size之間的關係,不斷進行調節即可。
關於查找,那就只有兩個地方可以找到median:如果是奇數,median肯定在maxheap的堆頂,直接輸出即可;若是偶數,我們需要取出兩個堆各自的堆頂元素,取其均值,再輸出。
class MedianFinder {
PriorityQueue<Integer> maxHeap;
PriorityQueue<Integer> minHeap;
/** initialize your data structure here. */
public MedianFinder() {
maxHeap = new PriorityQueue<>((a,b) -> b - a);
minHeap = new PriorityQueue<>((a,b) -> a - b);
}
public void addNum(int num) {
if(maxHeap.size() == 0|| num <= maxHeap.peek()){
maxHeap.offer(num);
}else{
minHeap.offer(num);
}
//exchange element because maxHeap must be larger than minHeap.
if (maxHeap.size() > minHeap.size() + 1){
minHeap.add(maxHeap.poll());
}else if (maxHeap.size() < minHeap.size()){
maxHeap.add(minHeap.poll());
}
}
public double findMedian() {
if(maxHeap.size() != minHeap.size()){
return maxHeap.peek();
}else{
return maxHeap.peek() / 2.0 + minHeap.peek() / 2.0;
}
}
}