《算法进阶50讲》中位数
Posted 英雄哪里出来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法进阶50讲》中位数相关的知识,希望对你有一定的参考价值。
文章目录
前言
一、概念
什么是中位数?对于一个序列,中位数就是所有数值排序之后位于中间的数值。如果总共有偶数个数,那么中位数就是所有数排序之后中间两个数的平均值。
1、奇数
如下图所示,这五个数排序以后,位于中的数是 4,所以中位数就是 4。
2、偶数
对于偶数的情况,排序以后,中间两个数是 4 和 7,所以中位数就是
4
+
7
2
=
5.500000
\\frac 4+7 2 = 5.500000
24+7=5.500000。
二、数据流中位数
前置算法:《画解数据结构》画解二叉堆
1、问题描述
设计一种容器,支持两种操作:
void addNum(int num)
- 从当前容器中添加一个整数。
double findMedian()
- 返回目前容器的中位数。
最多会对addNum
、findMedian
进行 1 0 5 10^5 105 次调用。
2、算法思路
(
1
)
(1)
(1) 准备两个堆,一个大顶堆,一个小顶堆。小顶堆中所有元素都是大于等于大顶堆的元素的。
(
2
)
(2)
(2) 如果能够保证两个堆元素的个数始终相差不超过 1,则可以在
O
(
1
)
O(1)
O(1) 的时间内快速得到整个序列的中位数;
(
3
)
(3)
(3) 如果总的元素个数是偶数,则为两个堆顶元素相加之和的平均数;如果总的元素个数是奇数,则为元素多的那个堆的堆顶,举个例子:
小顶堆元素 | 大顶堆元素 |
---|---|
9 8 7 | 5 4 3 2 |
9 8 7 | 5 4 3 |
9 8 7 5 | 4 3 2 |
(
4
)
(4)
(4) 那么,进行元素插入呢?我们发现,小顶堆的元素是数组大的那一半,大顶堆的元素是数组小的那一半,所以我们可以根据它和小顶堆堆顶的元素大小,来选择插入哪个堆。假设插入的数为
x
x
x,小顶堆堆顶的元素为
t
t
t,则:
(
4.1
)
(4.1)
(4.1)
x
≥
t
x \\ge t
x≥t, 直接插入小顶堆;
(
4.2
)
(4.2)
(4.2)
x
<
t
x < t
x<t,直接插入大顶堆;
插入完毕,有可能导致两个堆的数量差值超过 1,于是需要进行调整操作;
(
5
)
(5)
(5) 如果 小顶堆数量 < 大顶堆数量 - 1,则弹出大顶堆元素,放入小顶堆;反之,则将小顶堆元素弹出,放入大顶堆;
3、时间复杂度
(
1
)
(1)
(1) 插入:由于任何时候,在插入之前,两个堆的数据量差值不超过 1,所以插入以后必然不会超过 2,最多进行一次调整,一次调整的时间复杂度为
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
(
2
)
(2)
(2) 查找:由于每次查找就是找两个堆的堆顶,所以时间复杂度为
O
(
1
)
O(1)
O(1)。
4、源码分析
class MedianFinder
priority_queue<int, vector<int>, greater<> > minHeap;
priority_queue<int, vector<int>, less<> > maxHeap;
public:
MedianFinder() // (1)
while(!minHeap.empty()) minHeap.pop();
while(!maxHeap.empty()) maxHeap.pop();
void balanceHeap() // (2)
int minSize = minHeap.size();
int maxSize = maxHeap.size();
if(minSize - maxSize < -1)
minHeap.push( maxHeap.top() );
maxHeap.pop();
else if(minSize - maxSize > 1)
maxHeap.push( minHeap.top() );
minHeap.pop();
void addHeap(int num) // (3)
if(minHeap.empty())
minHeap.push(num);
return ;
if(num >= minHeap.top())
minHeap.push(num);
else
maxHeap.push(num);
void addNum(int num) // (4)
addHeap(num);
balanceHeap();
double findMedian() // (5)
int minSize = minHeap.size();
int maxSize = maxHeap.size();
if(minSize + maxSize == 0)
return 0;
if(minSize > maxSize)
return minHeap.top();
else if(minSize < maxSize)
return maxHeap.top();
return ( minHeap.top() + maxHeap.top() ) / 2.0;
;
(
1
)
(1)
(1) 初始化 大顶堆 和 小顶堆;
(
2
)
(2)
(2) void balanceHeap()
用于对两个堆进行平衡操作;
(
3
)
(3)
(3) addHeap(int num)
用于根据数据的大小关系,决定插入大顶堆还是小顶堆;
(
4
)
(4)
(4) 实现插入API(插入 + 平衡);
(
5
)
(5)
(5) 用
O
(
1
)
O(1)
O(1) 的时间复杂度计算中位数;
三、滑动窗口中位数
前置算法:
(
1
)
(1)
(1) 哈希表
(
2
)
(2)
(2) 离散化
(
3
)
(3)
(3) 二分枚举
(
4
)
(4)
(4) 前缀和
(
5
)
(5)
(5) 树状数组
1、问题描述
给你一个数组
nums
,有一个长度为 k k k 的窗口从最左端滑动到最右端。窗口中有 k k k 个数,每次窗口向右移动 1 位。找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
2、算法思路
(
1
)
(1)
(1) 我们先把前
k
k
k 个数丢到一个容器中;
(
2
)
(2)
(2) 如果
k
k
k 为奇数,则就是求这个容器的第
k
+
1
2
\\frac k+12
2k+1 大的数;如果
k
k
k 为偶数,则就是求这个容器的第
k
2
\\frac k2
2k 大的数 和
k
2
+
1
\\frac k2+1
2k+1 大的数的平均值;
(
3
)
(3)
(3) 然后,窗口滑动一格,其实就是从容器中删除一个元素、加入另一个元素。
(
4
)
(4)
(4) 于是,我们需要提供一种容器,能够支持以下三种操作:
(
4.1
)
(4.1)
(4.1) 插入;
(
4.2
)
(4.2)
(4.2) 删除;
(
4.3
)
(4.3)
(4.3) 查询
k
k
k 大数;
(
5
)
(5)
(5) 这个
k
k
k 大数的容器,我们可以采用树状数组来实现,利用 二分枚举答案 + 树状数组的求和 通过
O
(
l
o
g
2
2
n
)
O(log_2^2n)
O(log22n) 的时间复杂度快速找到第
k
k
k 大的数。
3、时间复杂度
(
1
)
(1)
(1) 插入:
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)
(
2
)
(2)
(2) 删除:
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)
(
3
)
(3)
(3) 求
k
k
k 大数:
O
(
l
o
g
2
2
n
)
O(log_2^2n)
O(log22n)
4、源码分析
class Solution
vector<int> bin;
unordered_map<int, int> value2Index;
int c[300000];
void discritizen(vector<int>& nums) // (1)
int i;
bin.clear();
for(i = 0; i < nums.size(); ++i)
bin.push_back(nums[i]);
sort(bin.begin(), bin.end()); // (1.1)
bin.erase( unique(bin.begin(), bin.end()), bin.end() ); // (1.2)
value2Index.clear();
for(i = 0; i < bin.size(); ++i) // (1.3)
value2Index[ bin[i] ] = i + 1;
// 根据 x 的值,返回需要操作数组对应下标
int getIndex(int x) // (2)
return value2Index[x];
int getValue(int index) // (3)
return bin[ index-1 ];
int getMaxIndex()
return bin.size();
int lowbit(int x)
return x & -x;
void add(int x, int n, int v) // (4)
while(x <= n)
c[x] += v;
x += lowbit(x);
int sum(int x) // (5)
int s = 0;
while(x)
s += c[x];
x -= lowbit(x);
return s;
int getkth(int K) // (6)
int l = 1, r 以上是关于《算法进阶50讲》中位数的主要内容,如果未能解决你的问题,请参考以下文章