作者: 负雪明烛
id: fuxuemingzhu
公众号: 每日算法题
本文关键词:LeetCode,力扣,算法,算法题,滑动窗口,中位数,multiset,刷题群


题目地址:https://leetcode.com/problems/sliding-window-median/

题目描述

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.

Examples:

[2,3,4] , the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Your job is to output the median array for each window in the original array.

For example,

Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position                Median
--------------- -----
[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
Therefore, return the median sliding window as [1,-1,-1,3,5,6].

Note:

  1. You may assume k is always valid, ie: k is always smaller than input array’s size for non-empty array.

题目大意

求一个滑动窗口的中位数。

解题方法

MultiSet 遍历到中位数位置

这个题是239. Sliding Window Maximum的变形。

题目的意思很简单,要求滑动窗口中的元素的中位数。如果是求平均数,就简单很多了:每次滑动窗口移动,都是增加一个元素、删除一个元素,因此窗口内元素的 是非常好维护的,再除以窗口的大小就能得到平均数。

中位数 是有序序列最中间的那个数。也就是说我们必须对窗口内的元素「排序」。我们知道排序的时间复杂度一般是 O(k * log(k)) ,还是比较高的。这个题如果对每个区间都进行分别排序肯定会超时,否则也不会是个 Hard 题目。

怎么快速求中位数呢?为了降低时间复杂度的一个绝招就是增加空间复杂度:利用更好的数据结构。是的,我们的目的是快速让一组数据有序,那就寻找一个内部是有序的数据结构呗!下面我分语言讲解一下常见的内部有序的数据结构。

  1. 在 C++ 中 set/multiset 是有序的集合,它们是基于红黑树实现的。其中 set 会对元素去重,而 multiset 可以有重复元素。

  2. 在 Java 中 TreeSet 是有序的集合,它也是基于红黑树实现的。 TreeSet 会对元素去重。

  3. 在 Python 中 heapq 实现了堆的算法,它不会对元素去重。

下面这个图是 C++ 的 multiset 内部结构示意图,它是个平衡二叉搜索树(BST),插入元素时会自动调整二叉树,使得每个子树根节点的键值大于左子树所有节点的键值,同时保证根节点左右子树的高度相等。这样二叉树高度最小,检索速度最快。它的中序遍历是有序的,另外它也允许出现重复的值。

当频繁的插入和删除元素时,这些有序的数据结构能够在在 O(log(k)) 的时间复杂度内维护结构的有序性。但是对于红黑树实现的数据结构来说,不能做随机读取,因此获取中位数的时候,也只能通过 O(k) 时间复杂度的查找。

有了非常高效的数据结构,做这个题已经不难了。我下面的代码演示了用 C++ 的 multiset 解决本题。代码不长,但是需要对 C++ 的 stl 有一些了解。

  1. 首先定义了结果数组 res 和 multiset;
  2. 遍历输入中的每个元素;
  3. 如果 multiset 中的元素超过了 k 个,需要把滑动窗口最左边 i - k 位置元素从 multiset 中删除(multiset 自动维护有序性);
  4. 把遍历到的当前元素插入到 multiset 中(multiset 自动维护有序性);
  5. 如果当前的位置达到了下标 k - 1,说明滑动窗口中有 k 个元素,开始求滑动窗口的中位数。

我们知道,如果数组 A 长度 k 是奇数时,令 mid = k / 2 ,那么中位数元素是 A[mid] ;如果数组长度 k 为偶数时,那么中位数是 (A[mid] + A[mid - 1]) / 2 。为了适应奇数和偶数长度,可以用通用的表达式 (A[mid] + A[mid - (1 - k % 2)]) / 2来求中位数。

求 multiset 里的中位数:我们先求 multiset 中所有元素的起始位置(最小元素),然后在此基础上让指针走 k / 2 步得到 mid ,最终 (A[mid] + A[mid - (1 - k % 2)]) / 2 就是我们要求的中位数。

时间复杂度: O(N*(log(k) + k))log(k) 每次插入和删除操作, k 是找中位数操作。

C++ 代码如下:

class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double> res;
multiset<double> st;
for (int i = 0; i < nums.size(); ++i) {
if (st.size() >= k) st.erase(st.find(nums[i - k]));
st.insert(nums[i]);
if (i >= k - 1) {
auto mid = st.begin();
std::advance(mid, k / 2);
res.push_back((*mid + *prev(mid, (1 - k % 2))) / 2);
}
}
return res;
}
};

MultiSet维护中位数指针

参考了grandyang大神的做法,维护中位数指针,这个中位数指针指向奇数区间的正中间或者偶数区间中间靠右的位置(e.g. [1,2,3,4]中的3)。既然每次会插入和删除元素各一个数字(总区间长度不变):当插入一个数字时,如果这个数字比中位数小,则中位数左边的数字变多,那么把中位数指针向左边移动一个位置;当删除一个数字时,如果小于等于中位数(这里的等于是因为可能中位数被删除),则中位数左边的数字变少,那么把中位数指针向右边移动一个位置。

为什么只比较小于等于中位数,不判断当插入和删除的数字大于中位数时要如何移动指针呢?这个和我们每次插入和删除各一个数字有关,如果插入和删除都是比中位数大的,那么中位数不用动;如果插入和删除都是比中位数小的,那么中位数向左一步向右一步也没变;如果插入和删除一个比中位数大一个比中位数小,那么中位数正好按照两个if中的一个移动一个位置,仍然指向中位数。

C++代码如下,时间复杂度是O(N),明显比上面的做法要快。

class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double> res;
multiset<double> st(nums.begin(), nums.begin() + k);
auto mid = next(st.begin(), k / 2);
for (int i = k; ;++i) {
res.push_back((*mid + *prev(mid, 1 - k % 2)) / 2);
if (i == nums.size()) return res;
st.insert(nums[i]);
if (nums[i] < *mid) --mid;
if (nums[i - k] <= *mid) ++mid;
st.erase(st.lower_bound(nums[i - k]));
}
return res;
}
};

参考资料:https://www.cnblogs.com/grandyang/p/6620334.html

日期

2019 年 9 月 14 日 —— 假期的生活就是不规律
2021 年 2 月 3 日 —— 坚持日更公众号10天了!

【LeetCode】480. 滑动窗口中位数 Sliding Window Median(C++)的更多相关文章

  1. Java实现 LeetCode 480 滑动窗口中位数

    480. 滑动窗口中位数 中位数是有序序列最中间的那个数.如果序列的大小是偶数,则没有最中间的数:此时中位数是最中间的两个数的平均数. 例如: [2,3,4],中位数是 3 [2,3],中位数是 (2 ...

  2. Leetcode 480.滑动窗口中位数

    滑动窗口中位数 中位数是有序序列最中间的那个数.如果序列的大小是偶数,则没有最中间的数:此时中位数是最中间的两个数的平均数. 例如: [2,3,4],中位数是 3 [2,3],中位数是 (2 + 3) ...

  3. LeetCode295-Find Median from Data Stream && 480. 滑动窗口中位数

    中位数是有序列表中间的数.如果列表长度是偶数,中位数则是中间两个数的平均值. 例如, [2,3,4] 的中位数是 3 [2,3] 的中位数是 (2 + 3) / 2 = 2.5 设计一个支持以下两种操 ...

  4. 滑动窗口(Sliding Window)技巧总结

    什么是滑动窗口(Sliding Window) The Sliding Problem contains a sliding window which is a sub – list that run ...

  5. 滑动窗口的中位数 · Sliding Window Median

    [抄题]: 给定一个包含 n 个整数的数组,和一个大小为 k 的滑动窗口,从左到右在数组中滑动这个窗口,找到数组中每个窗口内的中位数.(如果数组个数是偶数,则在该窗口排序数字后,返回第 N/2 个数字 ...

  6. Leetcode 239题 滑动窗口最大值(Sliding Window Maximum) Java语言求解

    题目链接 https://leetcode-cn.com/problems/sliding-window-maximum/ 题目内容 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧 ...

  7. [Swift]LeetCode239. 滑动窗口最大值 | Sliding Window Maximum

    Given an array nums, there is a sliding window of size k which is moving from the very left of the a ...

  8. 数据流滑动窗口平均值 · sliding window average from data stream

    [抄题]: 给出一串整数流和窗口大小,计算滑动窗口中所有整数的平均值. MovingAverage m = new MovingAverage(3); m.next(1) = 1 // 返回 1.00 ...

  9. [LeetCode] Sliding Window Median 滑动窗口中位数

    Median is the middle value in an ordered integer list. If the size of the list is even, there is no ...

随机推荐

  1. python12类

    self 表示类里面的对象的引用 python一般不需要去解决内存管理,解释器会进行自动给回收 #类鱼类之间空格两行,前面的函数里面也是两行,类里面的方法个一行 class Cat(object): ...

  2. Can't connect to HTTPS URL because the SSL module is not available. - skipping

    今天用pip3安装第三方库的时候报了这样一个错: Can't connect to HTTPS URL because the SSL module is not available. - skipp ...

  3. centos 安装reids

    1.安装tcl支持 yum install tcl 2.安装redis我们以最新的2.8.9为例 $ wget http://download.redis.io/releases/redis-2.8. ...

  4. 什么是DDL,DML,DCL

    转载自  https://www.2cto.com/database/201610/555167.html DML.DDL.DCL区别 . 总体解释: DML(data manipulation la ...

  5. Redis - 1 - linux中使用docker-compose安装Redis - 更新完毕

    0.前言 有我联系方式的那些半吊子的人私信问我:安装Redis有没有更简单的方式,网上那些文章和视频,没找到满意的方法,所以我搞篇博客出来说明一下我的安装方式吧 1.准备工作 保证自己的linux中已 ...

  6. CRLF漏洞浅析

    部分情况下,由于与客户端存在交互,会形成下面的情况 也就是重定向且Location字段可控 如果这个时候,可以向Location字段传点qqgg的东西 形成固定会话 但服务端应该不会存储,因为后端貌似 ...

  7. C++ 继续(3n+1)猜想

    1005 继续(3n+1)猜想 (25分)   卡拉兹(Callatz)猜想已经在1001中给出了描述.在这个题目里,情况稍微有些复杂. 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过 ...

  8. Linux学习 - 分区与文件系统

    一.分区类型 1 主分区:总共最多只能分四个 2 扩展分区:只能有一个(主分区中的一个分区),不能存储数据和格式化,必须再划分成逻辑分区                               才 ...

  9. struct vs class in C++

    在C++中,除了以下几点外,struct和class是相同的. (1)class的成员的默认访问控制是private,而struct的成员的默认访问权限是public. 例如,program 1会编译 ...

  10. C++易错小结

    C++ 11 vector 遍历方法小结 方法零,对C念念不舍的童鞋们习惯的写法: void ShowVec(const vector<int>& valList) { int c ...