滑动窗口,顾名思义就是一个在数组或者字符串中连续的一个子数组,或者沿着轨道开动”列车“,而定长滑动窗口,就是长度固定的列车,在数组中可以前后移动(移除/添加)

我们以LeetCode的一道例题来看。(感谢灵茶山艾府的Leetcode题单!本系列文章只是用自己的话把算法解释给自己看(?)希望自己日后Review的时候能看懂23333。)

643. 子数组最大平均数 I

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10E-5 的答案都将被视为正确答案。

从题目中我们可以看出,长度为 k的子字符串就是一个定长的滑动窗口!我们要找出这个窗口大小内最大平均数。

我们先确定好要维护的数,有三个!一个用于记录当前平均数 avg,一个用于记录最大平均数,也就是题目所求 maxavg,最后一个用于记录当前累计和 tempSum用于求平均数。、

接着以一个左右指针的思想进行窗口构建,R指针为快指针,L指针为慢指针,R指针先开始滑,记录滑过的数字的累计和,并更新L指针所在地方(因为是定长,所以可以轻易通过R-k+1得出左指针位置),当L为正时,说明窗口已经形成了,我们就进行题目要求的操作(算平均数、比较是否最大平均数),完成后,我们要当前指向L指针的元素从我们的窗口中剔除,以便迎接下一个循环开始时新来的数组小伙伴,从而维持窗口大小正确。

欸,为什么是R-k+1呢,我们来看下面几种情况:

[1,4) -> [1,2,3] -> 4-1 = 3

[1,4] -> [1,2,3,4] -> 4-1+1=4

(1,4) -> [2,3] -> 4-1-1=2

很明显,滑动窗口的意图是左右都闭合的情况,所以就是[L,R],在已知窗口长度为 k的情况下,求得左端点 R-k+1

这个模板按照 入->更新->出 的顺序,所以每一次循环结束的时候,滑动窗口都是等待前方新元素加入的临界状态。

class Solution {
    public double findMaxAverage(int[] nums, int k) {
        double avg = 0; 
        double maxavg = Integer.MIN_VALUE;
        double tempSum = 0;
        for (int R = 0; R < nums.length; R++) {
            int L = R - k + 1; //维护左指针
            tempSum += nums[R]; //将右边新遇见的数加入累计和中
            if (L < 0) { //如果L还小于0,证明窗口还没有形成
                continue; 
            } else { //否则,窗口已经形成,该干活了!做题目想要做的事情
                avg = tempSum / (R-L+1);
                maxavg = Math.max(avg, maxavg);
                tempSum -= nums[L]; //干完活之后,把左指针踢出去,让窗口缺口为1,以迎接下个循环的新元素!
            }
        }
        return maxavg;
    }
}

9.26练习更新:

子数组最大平均数 I,和例题发现有不一样!求的是平均数最大值,但是数中有负数,所以不能直接 int result=0;,而是用 int result=Integer.MIN_VALUE来确保result起始的时候是边界值!

11.29练习更新:

2090. 半径为 k 的子数组平均值有感,要学会正难则反的思想!题目要求的是以 k为半径构造窗口求平均数,如果不满足窗口大小则填入 -1如果按照左右指针分着来的话,处理构造窗口时以及结束窗口(不满足大小时填 -1)会非常非常麻烦,写了我一个多小时!!最后放弃了,看了题解才知道,一开始先把数组全部填-1,再把满足大小的地方填入对应的平均数,这样就一下子把难度降下来很多!

一些数组方法记录

Arrays.fill(result,-1);把数组全填-1

List转int[]

int[] arr = result.stream().mapToInt(Integer::intValue).toArray();