基本概念
滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。
分为:固定的,动态变化的
应用:
- 给出的数据结构是数组或者字符串
- 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
- 该问题本身可以通过暴力求解
思路:
- 初始时,左右指针left,right都指向第0个元素,窗口为[left,right),注意这里是左闭右开
- 开始循环遍历整个数组元素,判断当前right指针是否超过整个数组的长度,是退出循环,否则执行第3步
- 然后right指针开始向右移动一个长度,并更新窗口内的区间数据
- 当窗口区间的数据满足我们的要求时,右指针right就保持不变,左指针left开始移动,直到移动到一个不再满足要求的区间时,left不再移动位置
- 执行第2步
板子:
int left = 0,right =0;
while(right指针未越界){
char ch = arr[right++];
//右指针移动,更新窗口
...
//窗口数据满足条件
//对于固定窗口而言,就是窗口的大小>=固定值;
//对于动态窗口,就是从left出发,窗口不断扩充,第一次满足题意的位置
while(窗口数据满足条件){
//记录或者更新全局数据
...
//右指针不动,左指针开始移动一位
char temp = arr[left++];
//左指针移动,窗口缩小,更新窗口数据
...
}
//返回结果
...
}
样例:
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
if(s.size()<p.size())
return {};
int l = 0, r = -1;
vector<int> freq_s(26, 0), freq_p(26, 0), res;
// 初始化代码值
for( int i = 0 ; i < p.size() ; i++ ){
freq_p[p[i] - 'a' ]++;
freq_s[s[++r] - 'a' ]++;
}
if ( freq_s == freq_p )
res.push_back( l );
// 固定长度的滑动窗口
while( r < s.size()-1 ){
freq_s[s[++r] - 'a' ]++;
freq_s[s[l++] - 'a' ]--;
if ( freq_s == freq_p )
res.push_back( l );
}
return res;
}
};
class Solution {
public String minWindow(String s, String t) {
if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
return "";
}
//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
//ASCII表总长128
int[] need = new int[128];
int[] have = new int[128];
//将目标字符串指定字符的出现次数记录
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}
//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
while (right < s.length()) {
char r = s.charAt(right);
//说明该字符不被目标字符串需要,此时有两种情况
// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
// 2.循环已经开始一段时间,此处又有两种情况
// 2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
// 如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
// 2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
if (need[r] == 0) {
right++;
continue;
}
//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
if (have[r] < need[r]) {
count++;
}
//已有字符串中目标字符出现的次数+1
have[r]++;
//移动右指针
right++;
//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
while (count == t.length()) {
//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
if (right - left < min) {
min = right - left;
start = left;
}
char l = s.charAt(left);
//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
if (need[l] == 0) {
left++;
continue;
}
//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
if (have[l] == need[l]) {
count--;
}
//已有字符串中目标字符出现的次数-1
have[l]--;
//移动左指针
left++;
}
}
//如果最小长度还为初始值,说明没有符合条件的子串
if (min == s.length() + 1) {
return "";
}
//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
return s.substring(start, start + min);
}
}