滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。
应用:什么情况可以用滑动窗口来解决实际问题呢?
- 一般给出的数据结构是数组或者字符串
- 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
- 该问题本身可以通过暴力求解
理解什么是滑动窗口之后,学会这个算法的关键就是理解这两个指针是如何移动的。
滑动窗口法就是在不断地调整子序列地起始位置与终止位置,从而得出我们想要的结果。滑动窗口法的起始与终止节点的移动的目的即为求解子序列的最优化处理,其基本的思路如下:
- 定义双指针,初始值一致,双指针之间的内容为所谓的窗口,包括双指针所指的元素。
- 确定指针之间的最佳窗口内容的判断条件,即窗口内容扩大、缩小的条件。
- 优先移动终止节点,扩大窗口直至恰好满足判断条件,再移动起始节点一次缩小窗口
- 再次判断是否符合条件,符合,此时为一次所求解的子序列,记录所需参数,继续移动起始节点,不符合,起始节点不动,移动终止节点至恰好符合条件
- 重复第三、第四点,至终止节点到达数组最后一位。
- 输出所需参数
里面最为关键的就是节点的移动,怎么移动!什么时候移动!都是需要根据题目实际情况进行考量。
根据以上思路,可以抽象成以下模板:
int left = 0,right =0;
while(right指针未越界){
char ch = arr[right++];
//右指针移动,更新窗口
...
//窗口数据满足条件 对于固定窗口而言,就是窗口的大小>=固定值;对于动态窗口,就是从left出发,窗口不断扩充,第一次满足题意的位置
while(窗口数据满足条件){
//记录或者更新全局数据
...
//右指针不动,左指针开始移动一位
char tmp = arr[left++];
//左指针移动,窗口缩小,更新窗口数据
...
}
//返回结果
...
}
如果,采取暴力做法采取双层 for 循环方式不断循环寻找符合条件的子序列,时间复杂度为 O(n^2^) ,而如果采用滑动窗口法将减低至 O(n) ,执行效率大大提高。 LeetCode 题目链接:长度最小的数组
题目描述
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例:
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4] 输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1] 输出:0
提示:
- 1 <= target <= 109
- 1 <= nums.length <= 105
- 1 <= nums[i] <= 105
题目分析
题目简单分析一下,就是获取符合序列内数字之和不小于目标值的最小子序列的长度。思路基本如下:
- 初始化双指针为 0
- 缩小、扩大判断条件确定为 sum >= target
- 利用 for 循环开始右移终止指针,直至符合终止节点到数组最后一位
- 使用 while 循环实现起始指针的不断右移操作,一旦符合条件记录此时长度并与已有记录值比较,取最小值
- 输出长度记录值
class Solution {
// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
注:
-
将 result 初始值设置为 int 的最大值是为了方便后面的最小值比较,取零的话,输出结果将会一直为零,只能取最大值。
-
sum -= nums[left++] 的执行顺序为 sum - = nums[left]; left++;
-
一层 for ,一层 while ,时间复杂度不为 O(n^2^) 的原因为:对于每一个元素来说,while 循环只操作两次,而非 n 次,即 O(2*n) = O(n)