算法-滑动窗口的中位数(堆)

今天在网上刷了一道关于堆的题,感觉有所收获。因为在这里之前,之前从来没有接触过关于堆的题目。

题意:

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

样例:

 对于数组 [1,2,7,8,5], 滑动大小 k = 3 的窗口时,返回 [2,7,7]

 最初,窗口的数组是这样的:

 [ | 1,2,7 | ,8,5] , 返回中位数 2;

 接着,窗口继续向前滑动一次。

 [1, | 2,7,8 | ,5], 返回中位数 7;

 接着,窗口继续向前滑动一次。

 [1,2, | 7,8,5 | ], 返回中位数 7;

最初看到这个题,想到的方法是暴力,将给定的窗口里面数字从先到大排序,再去中间那个数就行了。但是,到后面发现超时了。于是乎,在网上搜索了相关解法,网上大多数使用的是用优先队列来操作。基本思路:用两个优先队列来模仿大顶堆和小顶堆(关于大顶堆和小顶堆的含义,这里不再详细的解释),然后操作两个队列里面的数据,选出中位数。

1.解题思想

(1).优先队列

用一个优先队列模仿大顶堆,用来装已经在窗口里面的数字的小的那一半,另一个队列则是小顶堆,装另一半。于是乎,剩下的那一个(没有进入任何一个队列)就是中位数

(2).最初的添加数据

我们假设两个队列在初始化都是空的,同时最初的时候,窗口的里面什么数据都没有,并且将当前的中位数设置为即将进入窗口的那个数。所以我们默认,向窗口加入数据是从第二个开始。

当我们加入一个数据时,判断它与当前的中位数(第一个数据作为中位数)的大小,如果它大于当前的中位数的话,则将它放入小顶堆;反之则放入大顶堆。放入过后(不论是大顶堆还是小顶堆),判断大顶堆的size与小顶堆的size,如果大顶堆的size大于小顶堆的size,则将当前的中位数放入小顶堆,从大顶堆取出一个数据(poll)作为新的中位数;如果小顶堆的size - 1都大于大顶堆的size,那么将当前的中位数放入大顶堆,从小顶堆中取出一个数据作为新的中位数。如图所示:

  问题: 为什么这里大顶堆的size大于小顶堆size就调整,而小顶堆的size - 1大于大顶堆的size才调整呢?

我们先来看看题:如果数组个数是偶数,则在该窗口排序数字后,返回第 N/2 个数字。这里说,如数字个数为偶数,则取 N/2。例如:窗口中有3个数字:1 2 3, 那中位数是 2,这个没有争议;但是如果窗口有4个数字:1 2 3 4 ,按照题意,选择 N/2,则是2,也就是说当前大顶堆为:4,而小顶堆:1 2。所以在小顶堆的size - 1大于大顶堆的size才调整,因为如果不调整的话,当前的中位数与当前窗口的数字的中位数不符合,因为窗口在这之前已经加入一个数字。

而加入一个数字,中位数有两种结果:当两个顶堆的size相等或者小顶堆的size - 大顶堆的size = 1时,中位数不变,因为两个顶堆的size平衡(我们可以这样认为,当前窗口中数字小的那一半在窗口的左边,大的那一半放在窗口的右边,而中位数在两个顶堆中间);当大顶堆的size大于小顶堆的size,表示实际的中位数偏向了大顶堆,所以将当前的中位数放入小顶堆去,从大顶堆中取出最大值作为当前的中位数(大顶堆中的所有数字小于当前的中位数,小顶堆的所有数字大于当前中位数,所以将当前的数字放入大顶堆)。总之一句话,就是加入一个数字后,必须保证当前的中位数就是实际的中位数。如图所示

(3).后续的添加数据

首先我们按照最初的添加数据步骤中得出的中位数,根据前面的规则来添加数据。

当添加一个过后,我们必须在两个顶堆中任意一个移除一个数据,这样才能保证当前窗口显示的数字。我们知道肯定移除当前窗口的第一个,怎么移除呢?首先当前窗口的第一个的值,这个值与当前的中位数来比较,如果大于中位数,则这个值在小顶堆中,从小顶堆移除就是了;如果小于中位数,表示这个值在大顶堆中,从大顶堆中移除;当等于中位数时,则判断当前小顶堆的size与大顶堆的size,如果大于大顶堆,则从小顶堆中取出一个数据作为新的中位数;反之,从大顶堆取出一个数据作为新的中位数。

问题:为什么当移除那个值与当前的中位数相等,要这样操作?

首先经过前一次添加数据,大顶堆的size与小顶堆的size要么相等,要么大顶堆的size - 小顶堆的.sze = 1。而这一次添加数据后,要么加入大顶堆,要么加入大顶堆,根据size判断,当大顶堆的size >= 小顶堆的size,表示当前添加数据小于当前的中位数,则实际上的中位数偏向了大顶堆了,所以从大顶堆中取出一个数据作为新的中位数;反之也是这样,从小顶堆中取出一个数据作为新的中位数也是这个道理。如图所示

(4).进一步的调整

如果在(3)中,移除之前,大顶堆的size - 小顶堆的.sze = 1,而且移除的数据在大顶堆中,那么实际的中位数就偏向了小顶堆,所以需要进一步调整;同时如果,本来大顶堆的size 等于 小顶堆的.sze ,而且移除的数据在小顶堆中,那么实际的中位数就偏向大顶堆。

2.代码

说完了解释,现在开始贴代码

 1     public class minComparator implements Comparator<Integer> {
 2         public int compare(Integer a, Integer b) {
 3             if (a > b)
 4                 return 1;
 5             else if (a == b)
 6                 return 0;
 7             else
 8                 return -1;
 9         }
10     }
11
12     public class maxComparator implements Comparator<Integer> {
13         public int compare(Integer a, Integer b) {
14             if (a > b)
15                 return -1;
16             else if (a == b)
17                 return 0;
18             else
19                 return 1;
20         }
21     }
22
23     public List<Integer> medianSlidingWindow(int[] nums, int k) {
24         List<Integer> res = new ArrayList<Integer>();
25         if (k == 0 || nums.length < k) {
26             return res;
27         }
28         PriorityQueue<Integer> maxQueue = new PriorityQueue<>();//大顶堆
29         PriorityQueue<Integer> minQueue = new PriorityQueue<>();//小顶堆
30         int media = nums[0];
31         //最初的添加数据
32         for (int i = 0; i < k; i++) {
33             if (media < nums[i]) {
34                 minQueue.offer(nums[i]);
35             } else {
36                 maxQueue.offer(nums[i]);
37             }
38             if (maxQueue.size() > minQueue.size()) {
39                 minQueue.offer(media);
40                 media = maxQueue.poll();
41             } else if (maxQueue.size() < minQueue.size() - 1) {
42                 maxQueue.offer(media);
43                 media = maxQueue.poll();
44             }
45         }
46         res.add(media);
47         //后续的添加数据
48         for (int i = 0; i < nums.length; i++) {
49             if (media < nums[i]) {
50                 minQueue.offer(nums[i]);
51             } else {
52                 maxQueue.offer(nums[i]);
53             }
54             //移除当前窗口第一个值
55             int old = nums[i - k];
56             if (old == media) {
57                 if (minQueue.size() > maxQueue.size()) {
58                     media = minQueue.poll();
59                 } else {
60                     media = maxQueue.poll();
61                 }
62             } else if (old < media) {
63                 maxQueue.remove(old);
64             } else {
65                 minQueue.remove(old);
66             }
67             //进一步调整
68             while (maxQueue.size() > minQueue.size()) {
69                 minQueue.offer(media);
70                 media = maxQueue.poll();
71             }
72             while (minQueue.size() < minQueue.size() - 1) {
73                 maxQueue.offer(media);
74                 media = minQueue.poll();
75             }
76             res.add(media);
77         }
78         return res;
79     }
时间: 2024-10-02 09:01:35

算法-滑动窗口的中位数(堆)的相关文章

滑动窗口的中位数

2017年8月7日 19:46:26 难度:困难 描述:给定一个包含 n 个整数的数组,和一个大小为 k 的滑动窗口,从左到右在数组中滑动这个窗口,找到数组中每个窗口内的中位数.(如果数组个数是偶数,则在该窗口排序数字后,返回第 N/2 个数字.) 样例: 对于数组 [1,2,7,8,5], 滑动大小 k = 3 的窗口时,返回 [2,7,7] 最初,窗口的数组是这样的: [ | 1,2,7 | ,8,5] , 返回中位数 2; 接着,窗口继续向前滑动一次. [1, | 2,7,8 | ,5],

算法--滑动窗口

转载请标明出处http://www.cnblogs.com/haozhengfei/p/a14049ec0869a8125a69f3af37471c77.html 滑动窗口练习题 第8节 滑动窗口练习题 有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置. 返回一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值. 以数组为[4,3,5,4,3,3,6,7],w=3为例.因为第一个窗口[4,3,5]的最大值为5,第二个窗口[3

数据结构与算法 -- 滑动窗口

什么是滑动窗口? 窗口,其实就是一个队列:滑动窗口,就是将这个队列朝着一个方向滑动,也就是将先进入队列的元素移出,重新往队列中添加元素. Leetcode 3.无重复字符的最长子串 题目描述:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1:输入: "abcabcbb",输出: 3 ,解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3. 示例 2:输入: "bbbbb",输出: 1,解释: 因为无重复字符的

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

[Swift]LeetCode480. 滑动窗口中位数 | 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 a

【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. 窗口位

TCP数据量--滑动窗口、拥塞窗口、慢启动、Negle算法 经受时延的确认等

TCP的数据流大致可以分为两类,交互数据流与成块的数据流.交互数据流就是发送控制命令的数据流,比如relogin,telnet,ftp命令等等:成块数据流是用来发送数据的包,网络上大部分的TCP包都是这种包. 很明显,TCP在传输这两种类型的包时的效率是不一样的,因此为了提高TCP的传输效率,应该对这两种类型的包采用不同的算法. 总之,TCP的传输原则是尽量减少小分组传输的数量. TCP的交互式数据流 ?         经受时延的确认技术 TCP的交互式数据流通常使用"经过时延的确认"

算法设计与优化策略——滑动窗口

"滑动窗口"和上篇博客中介绍的"等价转换"一样也为一种算法优化的思想.同样,下面通过一个例子,来介绍这种思想.唯一的雪花(Unique snowflake,UVa 11572)输入一个长度为n(n<=10^6)的序列A,找到一个尽量长的连续子序列AL~AR,使得该序列中没有相同的元素.在读完题目以后,我们不难有思路.最简单的思路就是,我们可以通过循环的方法,对每一个元素都找出一它为开头的最长序列(没有相同元素).这个方法也能做出来,但似乎有点太麻烦了.下面,我

粘包、拆包发生原因滑动窗口、MSS/MTU限制、Nagle算法

[TCP协议](3)---TCP粘包黏包 有关TCP协议之前写过两篇博客: 1.[TCP协议](1)---TCP协议详解 2.[TCP协议](2)---TCP三次握手和四次挥手 一.TCP粘包.拆包图解 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况: 1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包 2)服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包 3)服务端分两次读取到了数据包,