《剑指offer》:[64]数据流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数据排序后中间两个数的平均值。

例如:1,2,3,4,5的中位数为:3。1,2,3,4的中位数为:(2+3)/2=3。

方案一:采用Partition来解决。在[29]中我们讲过,快速查找中的Partition函数是十分重要,是一个比较常用的算法。所以这里我们采用partion函数来解决。从字符流里读字符,插入到一个无需的数组中的复杂度为O(1),查找中位数的时间复杂度为O(N)。

方案二:采用排序数组。采用插入排序,读取一个字符进行有序的插入操作。这样排序的时间复杂度为O(N*N),但是取得中位数的时间复杂度为O(1)。

方案三:考虑到方案二数组的插入需要移动数据,所以这里我们可以采用链表来解决,这样我们需要定义两个额外的指针指向中间结点。插入数据排序的时间复杂度为O(N),但是得到中位数的时间效率为:O(1)。

方案四:为了提高方案三中插入数据的效率,我们采用二叉排序树,此时的时间复杂度为O(logN),但是当二叉搜索树看起来不平衡看起来像个链表的时候,其插入的时间复杂度任然为O(logN)。为了得到中位数,我们可以在结点中添加一个表示结点数目的字段,有了这个字段我们可以在平均O(logN)时间得到中位数,但是最差情况任然需要O(N)的时间。

方案五:为了避免方案四中极度不平衡的情况,我们采用平衡二叉树(AVL树)。但是AVL树中的平衡因子是左右子树的高度差,我们可以将该平衡因子修改为左右子树的结点数目的差。有了这个改动,可以用O(logN)时间向该树中添加一个新的结点。同时用O(1)的时间来得到中位数的值。

方案六:采取大顶堆和小顶堆。虽然AVL树的效率较高,但是大部分编程语言函数库里没有实现这个数据结构,需要对平衡因子修改的同时还要在短时间内写出其实现代码有点儿困难。这里我们用两个容器来实现和方案五一样的效果。方法如下:

(1)用两个堆,一个大顶堆,一个小顶堆。将数据分割成两部分,左边的大顶堆饿数据都小于右边的小顶堆的数据。

(2)先往小顶堆里面存数,并保持: 0 <= 小顶堆的size()-大顶堆的size() <= 1

(3)保持两边数量几乎一致就需要在插入的时候进行比较、调整。

(4)返回中位数的时候,如果小顶堆和大顶堆size()相同,就返回他们堆顶元素的平均值;否则返回小顶堆的堆顶元素。

这种方法插入时间复杂度是O(log n),返回中位数的时间复杂度是O(1)

这样我们可以在O(logN)的时间复杂度里完成数据的插入,在O(1)的时间复杂度里完成中位数的提取操作。所以综合其时间复杂度为O(logN)。

核心类实现代码如下:

template<typename T>
class Heap
{
private:
	vector<T> min;
	vector<T> max;
public:
	void Insert(T num)
	{
		if(((min.size()+max.size())&1)==0)//偶数插入左边最大堆;
		{
			if(max.size()>0 && num<max[0])
			{
				max.push_back(num);
				push_heap(max.begin(),max.end(),less<T>());
				num=max[0];
				pop_heap(max.begin(),max.end(),less<T>());
				max.pop_back();
			}
			min.push_back(num);
			push_heap(min.begin(),min.end(),greater<T>());  

		}
		else  //奇数插入右边最小堆;
		{
			if(min.size()>0&&num>min[0])
			{
				min.push_back(num);
				push_heap(min.begin(),min.end(),greater<T>());
				num=min[0];
				pop_heap(min.begin(),min.end(),greater<T>());
				min.pop_back();
			}
			max.push_back(num);
			push_heap(max.begin(),max.end(),less<T>());  

		}
	}
	T get_median()
	{
		int size=min.size()+max.size();
		if(size==0)
			throw exception("no numbers are available");
		T median=0;
		if((size&1)!=0)  //如果是奇数返回最小堆的第一个元素;
		{
			median=min[0];
		}
		else  //如果是偶数则返回中间两个数的平均值;
		{
			median=(max[0]+min[0])/2;
		}
		return median;
	}
};

时间: 2024-08-23 13:43:52

《剑指offer》:[64]数据流中的中位数的相关文章

剑指offer:数据流中的中位数(小顶堆+大顶堆)

1. 题目描述 /** 如何得到一个数据流中的中位数? 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值. 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值. 我们使用 Insert()方法读取数据流,使用 GetMedian()方法获取当前读取数据的中位数. */ 2. 思路 /** 最大堆和最小堆 * 每次插入小顶堆的是当前大顶堆中最大的数 * 每次插入大顶堆的是当前小顶堆中最小的数 * 这样保证小顶堆中的数永远大于等于大顶堆中的数(值

[剑指Offer] 62.数据流中的中位数

题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值. 1 class Solution { 2 public: 3 vector<int> vec; 4 void Insert(int num) 5 { 6 vec.push_back(num); 7 } 8 9 double GetMedian() 10 { 11 sort(vec.begin(),v

《剑指offer》数据流中的中位数

[ 声明:版权所有,转载请标明出处,请勿用于商业用途.  联系信箱:[email protected]] 题目链接:http://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后

[剑指offer] 63. 数据流中的中位数

题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数. 用一个大顶堆和一个小顶堆,维持大顶堆的数都小于等于小顶堆的数,且两者的个数相等或差1.平均数就在两个堆顶的数之中. 原文地址:https://www.cnblogs.com/ruoh3kou/p/10261

剑指offer:数据流中的中位数

题目描述: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数. 思路分析: 思路一:最朴素的想法,用一个vector来存输入的数据流.在取中位数的函数中,每次对数据流进行一次排序,对于奇数长度的数据,直接取中间值,对于偶数长度的数据,取中间两个数的平均值.很显然,这个

剑指offer——43数据流中的中位数

题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数. 题解: 目前想不出更好的方法,待续更新... 1 class Solution { 2 private: 3 vector<int> min; 4 vector<int> max; 5 pub

【剑指offer】数据流中的中位数

题目链接:数据流中的中位数 题意:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数. 题解:啊我这题用暴力做的..正解应该是最大堆最小堆..和之前一个题一样.后面再补. 暴力就是sort一下找中位数. 代码: class Solution { public: vect

[剑指offer] 41. 数据流中的中位数 (大小堆,优先队列)

对于海量数据与数据流,用最大堆,最小堆来管理. class Solution { public: /* * 1.定义一个规则:保证左边(大顶堆)和右边(小顶堆)个数相差不大于1,且大顶堆的数值都小于等于小顶堆的数 * 2.大小堆顶可以用优先序列实现 插入规则: 当插入数值小于左边的堆顶时候,就插入左边,否则插入右边堆.(注意初始为空时,插入不能比较) 调整使得满足个数差<=1: 正常时是只有两种情况:p=q或者p=q+1,由于每插一个值就会考虑调整,那么边界情况就是p=q+2或者p+1=q p=

剑指offer63:数据流中的中位数

题目描述: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值. 本题最开始简单的理解为求中位数,使用的是快排的思想,当数据元素为奇数个时,求第n/2大的数,当元素个数为偶数时,先求n/2个数,然后对右边的求出一个最小值. 看了别人的做法,发现应该把这道题理解为一个在线算法题.关键是使用两个堆,最大化堆存储前n/2个数,最小化堆存储后n/2个数,当元素个数为偶数个

剑指offer (29) 数组中出现次数超过一半或1/3或1/N的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字 方法一:如果把这个数字排序,那么排序之后位于数组中间的数字一定就是出现次数超过数组长度一半的数字 这个数字就是统计学中的中位数,即长度为n的数组中第n/2大的数字 在数组中得到任意第k大数字,这一问题有O(n)解,注:这里第kth个元素,kth从1开始计数,并且重复元素不去重 (1) 直接sort排序,然后定位到索引为kth-1的元素 int FindKth1(std::vector<int>& num, int kt