leetcode 480.滑动窗口中位数
题干
中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[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 以内的答案将被视作正确答案。
思路
容易想到的思路是维护窗口内的数据使其有序,然后查找中位数即可,但显然排序+遍历的解法效率过低。
我们需要一种合适的数据结构,其内部元素有序,且能提供一种更高效的查找方法。
较好的解法是手撸支持重复元素的BST(平衡二叉搜索树),并实现根据位置(比如在k大的窗口中第3位,按照第3位这个数据)查找元素。
题解
也可以调用c++的扩展库pbds来实现红黑树
关于详细的用法可以参考链接
由于红黑树不支持重复元素,所以我们将树中的数据设为pair,同时nums数组中的下标作为标识符来避免这个问题。
tree的find_by_order方法会返回迭代器,优化了寻找窗口内中位数的效率。
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
//null_type无映射,less根据pair的第一个元素进行从小到大排列作为tree的策略,rb_tree_tag实现红黑树,tree_order_statistics_node_update更新节点
tree<std::pair<int, int>,null_type,std::less<std::pair<int, int>>,rb_tree_tag,tree_order_statistics_node_update> rbTree;
std::vector<double> ans;
std::queue<std::pair<int,int>> window;
int n = nums.size();
for(int i = 0 ; i < n ; ++i){
window.push({nums[i],i});
rbTree.insert({nums[i],i});
if(window.size() == k){
double midianPublic = (*rbTree.find_by_order(k / 2)).first;
double midianEven = (*rbTree.find_by_order(k / 2 - 1)).first;
double midian = k % 2 == 1 ? midianPublic : (midianPublic + midianEven) / 2;
ans.push_back(midian);
rbTree.erase(window.front());
window.pop();
}
}
return ans;
}
};