前言:
在引入滑动窗口概念之前,我们先举个例子,如力扣上的这条题目:
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
拿到题目之后最容易想到的方法就是暴力枚举。从字符s1开始遍历,当遇到与s1相同的字符s2时,记录下最长子字符串的长度,指针再回到字符s1的下一个位置,继续循环。
public int lengthOfLongestSubstring01(String s) {
char[] chars = s.toCharArray();
if(chars.length==0) return 0;
// 用哈希表记录当前字符串是否重复
HashMap<Character, Integer> map = new HashMap<Character,Integer>();
int max = 0; // 用于记录当前保存的最大值
int len = 0; // 多个循环中,当前循环的最大值
for(int i = 0; i < chars.length;i++){
if(map.containsKey(chars[i])){
max = Math.max(max,len);
len = 1;
// 如果当前字母已出现过,就从当前位置往下读取
i = map.get(chars[i]) + 1;
map.clear();
map.put(chars[i],i);
continue;
}
map.put(chars[i],i);
len++;
max = Math.max(len,max);
}
return max;
}
这种解法简单但效率不高。如在 "abcabcbb" 的字符串中,我们第一次遍历 "abca" 的时候,就知道其中的 "bca" 必然是不重复的。但是在在第二次遍历的时候,仍然要对 "bca" 进行遍历。
那么有没有一种方法,对已经遍历过的字符可以不用重复遍历呢?我们引入了滑动窗口的概念。
代码经过修改后如下:
public int lengthOfLongestSubstring01(String s) {
char[] chars = s.toCharArray();
if(chars.length==0) return 0;
// 用哈希表记录当前字符串是否重复
HashMap<Character, Integer> map = new HashMap<Character,Integer>();
int left = 0; // 左指针
int max = 0; // 多个循环中,当前循环的最大值
for(int right = 0; right < chars.length; right++){
// 当前字符
char cur = s.charAt(right);
// 如果遇到重复字符,左指针直接移动到该字符的下一位
// 注意因为map中存储的值一直存在,所以要确保左指针不会回退
if(map.containsKey(cur)){
left = Math.max(map.get(cur)+1,left);
}
// 更新最大长度值
max = Math.max(max,right - left+1);
// 更新哈希表中映射的下标(遇到重复值后更新成新的位置)
map.put(cur,right);
}
return max;
}
什么是滑动窗口?
-
滑动窗口是双指针的一种特例,可以称为左右指针。指的是在任意时刻,只有一个指针运动,而另一个保持静止。
-
滑动窗口路一般用于解决特定的序列中符合条件的连续的子序列的问题。
-
滑动窗口的时间复杂度是线性的,滑动窗口的左右边界都不会向左滑动,向左滑动等于走回头路,是一种回溯的算法,很可能会陷入死循环。
-
滑动窗口是一种全遍历问题,一定会遍历到末尾的。
其本质思路在于:
滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。初始化将滑动窗口压满,取得第一个滑动窗口的目标值。
继续滑动窗口,每往前滑动一次,需要删除一个和添加一个元素,求最优的目标值。
上述说明主要是两个while循环,可以简单抽象成一个模板如下:
int left = 0,right =0;
while(right指针未越界){
char ch = arr[right++];
//右指针移动,更新窗口
...
//窗口数据满足条件 对于固定窗口而言,就是窗口的大小>=固定值;
// 对于动态窗口,就是从left出发,窗口不断扩充,第一次满足题意的位置
while(窗口数据满足条件){
//记录或者更新全局数据
...
//右指针不动,左指针开始移动一位
char tmp = arr[left++];
//左指针移动,窗口缩小,更新窗口数据
...
}
//返回结果
...
}
滑动窗口的一般应用:
-
1、一般给出的数据结构是数组或者字符串
2、求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
-
3、该问题本身可以通过暴力求解