题目描述
中位数是有序序列最中间的那个数。如果序列的大小是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4]
的中位数是3
[2,3]
的中位数是(2 + 3) / 2 = 2.5
给定一个数组 nums
,有一个大小为 k
的窗口从最左端滑动到最右端。窗口中有 k
个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
样例
给出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3。
窗口位置 中位数
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
1 3 -1 [-3 5 3] 6 7 3
1 3 -1 -3 [5 3 6] 7 5
1 3 -1 -3 5 [3 6 7] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。
说明
- 你可以假设
k
始终有效,即:k
始终小于输入的非空数组的元素个数。 - 与真实值误差在
10^-5
以内的答案将被视作正确答案。
算法
(双堆) $O(n \log k)$
- 定义一个小根堆
min_heap
和一个大根堆max_heap
,小根堆的堆顶大于等于大根堆的堆顶。 - 保证小根堆的大小比大根堆的大小多 1 或相等。
- 按照上述的定义,小根堆的堆顶或者两个堆的堆顶的平均值就是中位数。
- 添加元素时,若小根堆为空或添加的元素大于等于小根堆堆顶,则添加进小根堆;否则添加到大根堆;
- 添加后,若两个堆的大小不满足
2
中的要求,则进行调整。 - 删除元素时,只需要在小根堆或大根堆删除一个等于该元素值的元素即可。
- 由于此题涉及删除操作,所以堆的容器采用
multiset
,方便操作。
时间复杂度
- 由于堆的大小最大为 $k$,在
multiset
中添加或删除元素的时间复杂度为 $O(\log k)$,共需要操作 $n$ 次,故总时间复杂度为 $O(n \log k)$。
空间复杂度
- 需要额外 $O(n)$ 的空间存储堆。
C++ 代码
class Solution {
public:
multiset<int> min_heap, max_heap;
multiset<int>::iterator it;
void insert(int x) {
if (min_heap.size() == 0 || x >= *min_heap.begin())
min_heap.insert(x);
else
max_heap.insert(x);
if (min_heap.size() > max_heap.size() + 1) {
it = min_heap.find(*min_heap.begin());
max_heap.insert(*it);
min_heap.erase(it);
} else if (max_heap.size() > min_heap.size()) {
it = max_heap.find(*max_heap.rbegin());
min_heap.insert(*it);
max_heap.erase(it);
}
}
void erase(int x) {
it = max_heap.find(x);
if (it != max_heap.end()) {
max_heap.erase(it);
} else {
it = min_heap.find(x);
min_heap.erase(it);
}
if (min_heap.size() > max_heap.size() + 1) {
it = min_heap.find(*min_heap.begin());
max_heap.insert(*it);
min_heap.erase(it);
} else if (max_heap.size() > min_heap.size()) {
it = max_heap.find(*max_heap.rbegin());
min_heap.insert(*it);
max_heap.erase(it);
}
}
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<double> ans;
for (int i = 0; i < k - 1; i++)
insert(nums[i]);
for (int i = k - 1; i < n; i++) {
insert(nums[i]);
if (k & 1) ans.push_back(*min_heap.begin());
else ans.push_back((0.0 + *min_heap.begin() + *max_heap.rbegin()) / 2);
erase(nums[i - k + 1]);
}
return ans;
}
};
现在数据加强了,要在min_heap.begin() + max_heap.rbegin()加上一个0ll
请问助教,这里的multiset的find看文档(http://www.cplusplus.com/reference/set/multiset/find/)说如果有多个目标值返回其中一个,是指随机其中一个还是类似lower_bound或者upper_bound返回最小或者最大的那个呢?答案里面看起来min_heap 应该找到lower_bound的那个而max_heap应该找到upper_bound的那个,但这里都是用的find, 有点疑惑,谢谢助教!
这里的 find 是删除里要用的,没必要关心是哪一个
明白了,谢谢助教!