滑动窗口顾名思义在一段数组中从头至尾选中符合标准子数组,类似于一个滑动的窗口。
i作为窗口起始值,j作为窗口结束值,通过不断移动起始值和终止值来获取所有符合标准的子数组。
滑动窗口法类似于双指针法,本质上是两个不同步指针的使用
做法:首先保持起始值 i=0 不变,不断移动 j 的位置,直到满足标准停下,再移动 i 的位置直到不满足条件以后停止,此时 j 之前的所有满足条件的子数组都完成了遍历,再重复上述操作直至完成对整个数组的遍历。
例1:Leetcode 209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT32_MAX;//赋给result最大值确保在第一次循环中大于len
int length = nums.size();
int front = 0;
int sum =0;
int len =0;
for(int end =0;end<length;end++)
{
sum += nums[end];//移动j的位置
while(sum>=target)//满足条件后
{
len = end + 1 - front;//获取满足条件的子数组的长度
result = result>len?len:result;//获取最短子数组
sum -= nums[front++];//移动i的位置
}
}
return result==INT32_MAX?0:result;//若没有符合条件的子数组则result不会被赋值,输出0
}
};
例2:Leetcode 904. 水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
题意提取可得本题是求一个数组内最长的且只有两个元素的连续子数组,同理此题可使用滑动窗口即双指针法:
以 i 为起始值,j 为终止值,移动 j 至第三种元素位置后停止
由于题目只要求两个不同的元素,即可令 i = j - 1 , 保持 j 的位置不变再将 i 向前移动直到同类元素最前端位置,重复上述操作直至 j 位移至数组末尾。
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int length = fruits.size();
int front = 0;//定义起始值
int result = 0;//返回数组长度
int len = 0;//记录每个子数组长度
int flag = 0;//标志位 flag==0时才可以对第二个元素赋值
int FirstFruit = 0;//第一个元素
int SecondFruit = 0;//第二个元素
for (int end = 0; end < length; end++)//开始移动终止位
{
FirstFruit = fruits[front];//第一个元素为起始位元素
if (FirstFruit != fruits[end] && flag == 0)
{
SecondFruit = fruits[end];//第二个元素为起始位后第一个不同的元素
flag = !flag;//标志位取反
}
if(SecondFruit != fruits[end] && FirstFruit != fruits[end] && flag == 1)//当出现第三种元素后终止位停止运动,起始位开始变换
{
front = end - 1;//起始位变为终止位前一个元素
while(fruits[front] == fruits[front - 1])
{
front--;//移动起始位至首个相同元素
}
flag = !flag;//标志位取反,可重新为第二元素赋值
end--;//保持终止位不移动
}
len = end - front + 1;//计算符合标准的子数组长度
result = result < len ? len : result;//取长度最长值
}
return result;
}
};
例3:Leetcode 76.最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
由于刚开始刷Leetcode,官方模板解答和大神解答有些细节方面我有点没看懂,但是按照滑动窗口的方法去解总归是对的,于是我就按照自己的想法编写了一下,通过时间和内存占有量都不是很好,但起码是通过了,写的很稚嫩,大神勿看。
按照滑动窗口的思想,无非就是一快一慢两个指针形成一个从左至右滑动的窗口遍历所有符合条件的子字符串,并将符合题目最终要求的子字符串输出。
所以最重要的就是确定2个最重要的条件:
1)标志窗口最右边的快指针什么时候开始移动和停止;
2)标志窗口最左边的慢指针什么时候开始移动和停止;
如图所示,当 j 开始移动到C的位置时满足了 t 的条件所以停止,此时就应该移动 i 的位置直到不满足 t 的条件再移动 j 如此循环往复直至遍历完所有子字符串。
我们可以定义两个字符串,一个字符串为 j 移动条件字符串condition,另一个字符串为重复量储存字符串rep。
1)定义一个条件字符串condition,初始值是字符串 t 的值,当 j 移动到的字符与condition中字符相等时删除condition中这一字符,例如 j 指向的字符是A,则删除condition中的A,直到condition为空后停止 j 的移动。
2)在窗口不断拓展的过程中,难免会遇到重复的字符,我们将他们都储存起来,在 i 移动时作为停止条件之一。
当condition为空后,即所有 t 中存储的字符都包含在窗口之中,我们开始移动窗口最左侧缩小窗口位置,当 i 移动到的字符与rep中的字符有相同时,删除rep中相似字符以确保窗口能够缩到最小。
当 i 移动到的字符满足条件 t ,却不再rep之中时,我们此时就获得了最小窗口,i 再向右一格后停止,窗口就不符合条件 t 需要再次将 j 向右移动使窗口满足 t 的条件,我们将窗口所缺的条件字符即 i 停止后它的前一个字符赋值给condition,让其作为 j 再次停止的条件。如图中所示,A成为了 j 再次停止移动的条件,j 不断向右移动直到再次找到A或抵达最右端停止。
class Solution {
public:
string minWindow(string s, string t) {
int len = s.size();
int front = 0;//窗口左值
int flag = 0;//初值标准位
string condition = t;//为condition赋初值
string rep = "";//rep初值为空
string result ="";//输出最小字符串
string save = "";//所有满足条件的字符串
for (int end = 0; end < len; end++)//窗口右值开始移动
{
if (t.find(s[end]) != string::npos )//判断字符串t中有没有字符s[end]
{
if (condition.find(s[end]) != string::npos)//判断字符串condition中有没有字符s[end]
{
condition.replace(condition.find(s[end]), sizeof(s[end]), "");//有就删掉
}
else
{
rep += s[end];//没有就是重复的,加入rep中
}
}
while (condition.empty())//开始移动窗口左值
{
save = s.substr(front, end - front + 1);//将窗口中的字符串赋给save
if (flag == 0)//给result赋初值
{
result = save;
flag = !flag;
}
if (rep.find(s[front]) == string::npos && t.find(s[front]) != string::npos)//如果i所移动到的元素是满足条件t且在窗口中不重复,则在向前移动一位后停止
{
result = result.size() < save.size() ? result : save;//获取最短字符串
condition += s[front];//更新窗口右值移动条件
front++;//左值再移动一位
break;
}
if (rep.find(s[front]) != string::npos)
{
rep.replace(rep.find(s[front]), sizeof(s[front]), "");//如果有重复就删掉
}
front++;//若不满足停止条件窗口左值持续移动
}
}
return result;
}
};
tips:
1.str.find(‘a’)返回a在字符串str中的位置,若没有则返回值为-1但是类型是string::size_type,实际上就是string::nops,如果没有找到可以直接写成str.find(‘a’) == string::nops
2.replace(local,size,"")
local是字符串中的位置,size是替换字符串占几个字节,最后一个值是准备替换的字符串的值,代码里替换空的字符就可以看成删除。