介绍
滑动窗口法,也叫尺取法(可能也不一定相等,大概就是这样 =。=),可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
引入的小例子
Leetcode 209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
这道题目最简单的解法自然是枚举每个数组起点和终点,这种解法的时间复杂度是O(N^2)
def
通过分析可以发现,这种解法进行了很多重复计算,首先是对于状态的重复计算,比如当start为0时,我们计算了区间[0,0], [0,1], [0,2],...等的和,但当start为1时,我们又重新计算了区间[1,1], [1,2], ....,的和,但事实上,这些区间的值是可以根据上一次计算的结果直接得到的,如区间[1,2]等于区间[0,2]减去nums[0]的值。换句话说,我们可以根据之前计算得到的结果来推断还未进行计算的结果,这也为剪枝带来了可能。
考虑这样一个例子,给定数组为[2,3,1,2,4,3],并给定要求的最小和s为7,通过第一次枚举,我们得知子数组[2,3],[2,3,1]都是小于7的,那我们也就没有必要在接下来的阶段对子数组[3],子数组[3,1]进行枚举检查了,因为他们的和一定是小于7的。若实现了这种剪枝,时间复杂度便可以得到大幅的优化。
那么如何实现这样的剪枝呢?考虑这样一种情形,数轴上存在一个滑动窗口,假设其左右端点分别为L和R。首先我们移动R,使得滑动窗口的区间满足给定的条件,然后我们再移动L,直到滑动区间不再满足给定的条件,如此循环往复,并在其过程中记录最优值。继续之前的例子,如图所示,过程如下:
- 滑动窗口的长度为0,位于数轴的最左端
- 滑动窗口右端R开始移动,直到区间满足给定的条件,也就是和大于7,停止于第三个元素2,记录下来当前的最优长度为4
- 滑动窗口左端L开始移动,并停止于第一个元素3,此时区间和为6,使得区间和不满足给定的条件
- 滑动窗口右端R继续移动,停止于第四个元素4,在过程中,最优长度仍然为4
- 滑动窗口左端L移动至第三个元素2,过程中更新最优长度为3
- 滑动窗口右端R移动至最后一个元素3,
- 滑动窗口左端L移动至最后一个元素,并在过程中更新最优长度为2
滑动窗口法的大体框架
通过归纳,我们可以勾勒出滑动窗口法的大体框架(只是基本框架,根据不同的问题应适当变动,重在把握精神)
初始化窗口端点
滑动窗口法实例
Leetcode 209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
这道题是上面讲解过的题目,这里套用之前提出的框架再讲一遍。我们设置一个状态为summation,表示当前区间的和,而状态满足的条件是summation >= s,寻找最优值则是去比较当前的最优值以及目前滑动窗口的长度。代入框架即得到了求解该问题的程序:
def
Leetcode 3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
这道题是寻找无重复字符的最长子串,我们设置一个set来保存当前区间内的字符。对于右端点而言,S[R]不在set当中即满足条件。对于左端点而言,只要让左端点移动到目前S[R]的值第一次出现的位置后面即可,也就是说,不让滑动窗口包含重复的字符(因为重复的字符一定是当前右端点指向的字符)。这道题和前面一道题不同的地方在于,前一道题右端点是从不满足给定条件到移动满足给定条件,而这道题则相反。因此右端点会移动到第一次不满足条件的位置,而左端点则移动到再一次满足条件的位置。代码如下:
def
Leetcode 1004. 最大连续1的个数 III
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
这道题和上一题一样,右端点移动到第一次不满足条件的位置,而左端点则移动到再一次满足条件的位置。需要满足的条件是,K值大于等于0。当碰到零时,K减去1。代码如下所示:
def
总结
滑动窗口法可以用来解决一些查找满足一定条件的连续区间的性质(长度等)问题,个人认为可以看做是一种双指针方法的特例,两个指针都起始于原点,并一前一后向终点前进。还有一种双指针方法,其两个指针一始一终,并相向靠近,这种方法的内在思想和滑动窗口也非常类似,如Leetcode11. 盛最多水的容器就可以使用这种解法求解。