AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

LeetCode 480. 滑动窗口中位数    原题链接    困难

作者: 作者的头像   wzc1995 ,  2018-07-01 22:06:29 ,  所有人可见 ,  阅读 2426


6


题目描述

中位数是有序序列最中间的那个数。如果序列的大小是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。

例如:

  • [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)$
  1. 定义一个小根堆 min_heap 和一个大根堆 max_heap,小根堆的堆顶大于等于大根堆的堆顶。
  2. 保证小根堆的大小比大根堆的大小多 1 或相等。
  3. 按照上述的定义,小根堆的堆顶或者两个堆的堆顶的平均值就是中位数。
  4. 添加元素时,若小根堆为空或添加的元素大于等于小根堆堆顶,则添加进小根堆;否则添加到大根堆;
  5. 添加后,若两个堆的大小不满足 2 中的要求,则进行调整。
  6. 删除元素时,只需要在小根堆或大根堆删除一个等于该元素值的元素即可。
  7. 由于此题涉及删除操作,所以堆的容器采用 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;
    }
};

4 评论


用户头像
飛燕   2023-10-07 20:43         踩      回复

现在数据加强了,要在min_heap.begin() + max_heap.rbegin()加上一个0ll


用户头像
一只菜鸡   2019-09-17 09:31         踩      回复

请问助教,这里的multiset的find看文档(http://www.cplusplus.com/reference/set/multiset/find/)说如果有多个目标值返回其中一个,是指随机其中一个还是类似lower_bound或者upper_bound返回最小或者最大的那个呢?答案里面看起来min_heap 应该找到lower_bound的那个而max_heap应该找到upper_bound的那个,但这里都是用的find, 有点疑惑,谢谢助教!

用户头像
wzc1995   2019-09-17 10:00         踩      回复

这里的 find 是删除里要用的,没必要关心是哪一个

用户头像
一只菜鸡   2019-09-18 03:40    回复了 wzc1995 的评论         踩      回复

明白了,谢谢助教!


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息