滑动窗口
从字面上来看,很容易理解,就是一个窗口,向前一步步移动,遇到的元素,我们窗口某个特性(比如窗口内所有元素的和)增加这个元素,与此同时,我们的窗口左边可以做一些收缩,改变窗口的大小,或着保持一定的长度。而后,right 继续向前移动,直到遍历结束。下面我们看一道例题感受下。
核心:利用指针
必须要深入探究窗口形成的条件.
https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters/
有时候,不容易找出滑动窗口,滑动窗口成立的条件不存在,为此我们自己要创造 条件,如这一道题中的 “p” 就是 最长重复字符中字符种类的数量,1-26 个
同时维护两个窗口
这题还看不懂 …
典型-求 k 个连续数组之和的最大值
1 |
|
从上面那道题中,我们看到 sum 的值一直随着左右指针的移动,不断改变着,一直 以 0(1)的时间复杂度,计算着长度为 k 的连续数组的元素之和。
滑动窗口问题关键字眼
连续,最多,强调顺序性,一组之内的差,和,乘积,一组之内最多做少能有多少个字符,字符替换等等
滑动窗口的求解
双指针
左指针 也可能一步步的移动,也可能跳跃式收缩,窗口的长度可固定,可不固定。右指针,大部分都是一步步移动
移动的过程,我们要对窗口内元素组合 而成的 某个 特性(如:和,乘积)做相应的 增加,减少。
最优解,就是在每次窗口元素结合特性的最优值
窗口内有时候需要借助 set,数组来 维护 “某种特性”
解析复杂题意
窗口滑动,有时候,并不太明确的把窗口找出来,不知道怎么滑,或者很复杂,不知道如何下手,那么我们就要转化题意了。
我们来看下列的一道题:
1004. 最大连续 1 的个数 III
1 |
|
翻转数字,最大连续 1 的个数,如果直接来的话,都不知道如何操作,也不知道如何把这个窗口给找出来。
这个时候就得转换题意了。
把「最多可以把 K 个 0 变成 1,求仅包含 1 的最长子数组的长度」转换为 「找出一个最长的子数组,该子数组内最多允许有 K 个 0 」。 转化成这样,那么这道题解就出来了。
1 |
|
窗口的大小不固定伸缩
解决的方案中特点
- 一定长度的数组 窗口
- 双指针 同向移动
- 每移动一次 在窗口中 构建解
- 窗口中的 特性(奇数,偶数,x 的数量)移动 1 次,特性要相应更新
下面来一道题,从双暴力之上优化而来的窗口滑动。
爱生气的书店老板
暴力解法
1 |
|
窗口滑动优化
1 |
|
如果题意要求涉及字母的比较,我们可以用一个数组进行计数.
1 |
|
1 |
|
题解
一开始很容易想到,直接求 ab 的排列,然后遍历进行窗口内的字字符串的比较,这很明显是暴力解,不优美,那么我就想了,
怎么能直接比较呢,我就观察 他们的比较规律,其实转换问题就是
s1 长度的每一个字符出现的次数 与 s2 中 s1 长度 的每一个字符出现的次数相等
那么我们就可以求出我们的解了,怎么统计字母出现的个数呢?我记得以前用数组表示过,所以我们就有了如下代码,
保持 s1 长度的窗口口,一直在 s2 中移动,与此同时,比较是否一致,那么一致 ,返回 true,否则返回 false
1 |
|
大佬心得。
什么情况下会想到滑动窗口法:
任何题目如果没有思路其实都可以想一下暴力解法。这道题暴力解法思路简单:
遍历任意 i,j,使得 i 和 j 之间的子串长度,等于 p 串的长度。该子串称之为 x。该步复杂度为 O(n)。
判断 x 是否与 p 是异位词。是的话,则把 i 加入答案中。该步复杂度为 O(n)。
暴力法的复杂度为 O(n^2)。显然不高效。
可以发现第二步其实做了很多不必要的操作,例如[i, j]和[i+1, j+1]两个子串在暴力法第二步中,需要各遍历一次,完全没必要。其实[i+1, j+1]完全可以在[i, j]的基础上做判断,也就是去掉头部的字符(i 位置),加上尾部的字符(j+1 位置)。这样第一步的复杂度可以降到 O(1)。整体复杂度降到 O(n)。已经得到信息不重复使用就浪费了,没必要重新搜集近乎相同的信息。这就是滑动窗口法。
滑动窗口法的特点是,一连串元素的信息,可以用常数时间推断出,该串整体移位后,新串信息。
所有滑动窗口问题,如果能从暴力法优化的角度思考,都不难想到。
求滑动窗口内的最大值和最小值。常见的方法有:
使用 multiset、TreeMap 等数据结构;
单调递增队列或者单调递减队列;