JAVA程序设计:数据流的中位数(LeetCode:295)

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[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 {

	static Comparator cmp=new Comparator()
	{
		public int compare(Integer e1,Integer e2)
		{
			return e2-e1;
		}
	};
	PriorityQueue q1;
	PriorityQueue q2;
    /** initialize your data structure here. */
    public MedianFinder() {
        q1=new PriorityQueue<>();
        q2=new PriorityQueue<>(cmp);
    }
    
    public void addNum(int num) {
        q1.add(num);
        q2.add(q1.peek());
        
        if(q1.size()q2.size()?(double)q1.peek():(q1.peek()+q2.peek())*0.5;
    }
}

方法二(基于C++):

自平衡二进制搜索树(如AVL树)具有一些非常有趣的特性。它们将树的高度保持在对数范围内。因此,插入新元素具有相当好的时间性能。中值总是缠绕在树根或它的一个子树上。使用与方法 3 相同的方法解决这个问题,但是使用自平衡二叉树似乎是一个不错的选择。但是,实现这样一个树并不是简单的,而且容易出错。

大多数语言实现模拟这种行为的是 multiset 类。唯一的问题是跟踪中值元素。这很容易用指针解决!

我们保持两个指针:一个用于中位数较低的元素,另一个用于中位数较高的元素。当元素总数为奇数时,两个指针都指向同一个中值元素(因为在本例中只有一个中值)。当元素数为偶数时,指针指向两个连续的元素,其平均值是输入的代表中位数。

算法:

两个迭代器/指针 lo_median 和 hi_median,它们在 multiset上迭代 data。
添加数字 num 时,会出现三种情况:
容器当前为空。因此,我们只需插入 num 并设置两个指针指向这个元素。
容器当前包含奇数个元素。这意味着两个指针当前都指向同一个元素。
如果 num 不等于当前的中位数元素,则 num 将位于元素的任一侧。无论哪一边,该部分的大小都会增加,因此相应的指针会更新。例如,如果 num 小于中位数元素,则在插入 num 时,输入的较小半部分的大小将增加 11。
如果 num 等于当前的中位数元素,那么所采取的操作取决于 num 是如何插入数据的。
容器当前包含偶数个元素。这意味着指针当前指向连续的元素。
如果 num 是两个中值元素之间的数字,则 num 将成为新的中值。两个指针都必须指向它。
否则,num 会增加较小或较高一半的大小。我们相应地更新指针。必须记住,两个指针现在必须指向同一个元素。
找到中间值很容易!它只是两个指针 lo_median 和 hi_median 所指元素的平均值。

class MedianFinder {
	multisetdata;
	multiset::iterator low,high;

public:
	MedianFinder():low(data.end()),high(data.end()){
	}
	void addNum(int num){
		const size_t n=data.size();
		data.insert(num);

		if(!n)
			low=high=data.begin();
		else if(n&1)
		{
			if(num<*low)
				low--;
			else
				high++;
		}
		else
		{
			if(num>*low && num<*high)
				low++,high--;
			else if(num>=*high)
				low++;
			else
				low=--high;
		}
	}

	double findMedian(){
		return (*low+*high)*0.5;
	}
};

 

你可能感兴趣的:(JAVA程序设计:数据流的中位数(LeetCode:295))