滑动窗口本质上是对于暴力算法的优化,采用的是双指针算法中的左右指针,对于暴力算法的优化。
滑动窗口的基本模板
while(right<s.size())
{
char c=s[right]
right++;
...//更新数据
while(valid==needs.size())
{
... //一般用于更新答案
char d=s[left];
left++;
... //更新数据
}
}
/*windows:表示窗口 needs:表示题目要求找出的东西的集合
left 左指针, right 右指针
valid 表示已经满足东西的种类
valid==needs.size() 表示题目的要求已经满足,可以开始缩小窗口*/
思考四个问题
1.移动right扩大窗口,即加入字符时,需要更新哪些数据
2.什么条件下,窗口应该暂停扩大,开始移动left缩小窗口
3.当移动left缩小窗口,即移出字符时,应该更新哪些数据
4.我们要的结果是在扩大窗口时还是在缩小窗口时更新
四个问题在滑动窗口的问题中都会出现,但是每个不同的题目,这四个问题的答案都是不同的,应该具体问题具体分析
1.最小覆盖子串
题目的要求:窗口中包含子串所有的字符,且数量一致
四个答案:
1.移动right扩大窗口,即加入字符时,需要更新哪些数据
移动right时,需要更新窗口中的数据(windows),当前已满足元素(valid)
2.什么条件下,窗口应该暂停扩大,开始移动left缩小窗口
窗口已经满足题目要求了,即valid==needs.size(),窗口已包含子串元素个数==子串元素总个数
3.当移动left缩小窗口,即移出字符时,应该更新哪些数据
移出字符时,应该判断窗口元素是否减小,减小了的话valid是否减小
4.我们要的结果是在扩大窗口时还是在缩小窗口时更新
缩小窗口,因为扩大窗口是让我们满足完全包含子串中的元素,缩小窗口是为了让我们找到满足要求的最小窗口
#include <algorithm>
#include <iostream>
#include <map>
using namespace std;
/*移动窗口思路:窗口不满足要求时,右侧向右扩展,扩展窗口
窗口满足要求时,左侧向右,缩小窗口
题目要求:包含子串中的所有字母
步骤:1.把子串所有元素放入哈希表needs中
2.右指针遍历母串,遇到一个属于子串的元素就放入窗口中,直到窗口满足要求
3.窗口满足要求后,开始缩小窗口,不断更新值,直到窗口小到不能满足要求
*/
string minwindows(string t, string s)
{
const int MAXINT = 0x3f3f3f;
map<char, int> windows, needs;
//windows包含放入窗口的在needs内的字母 needs包含t的字母
int start = 0, len = MAXINT;
int left = 0, right = 0;
int valid = 0; //已满足的字符种类
for (char c : t)
needs[c]++;
while (right < s.size())
{
char c = s[right];
right++; //右移窗口
//更新,向右扩展到满足要求:正好包含字符种类
if (needs.count(c))
{//c是被需要的
windows[c]++;
if (windows[c] == needs[c])
valid++; //只有一种字母数量满足要求,valid++
}
//满足要求后,开始缩小窗口
while (valid == needs.size())
{
//更新答案
if (right - left < len)
{
start = left;
len = right - left;
}
char d = s[left];
left++; //左移窗口,找到最小
//缩小的时候要更新窗口中的值
if (needs.count(d))
{//d在needs中,左移窗口被舍弃,需要更新
if (windows[d] == needs[d]) //被舍弃后不满足要求,valid需要-1
valid--;
windows[d]--;
}
}
}
return len == MAXINT ? "" : s.substr(start, len);
}
int main()
{
string s = "ADOBECODEBANC", t = "ABC";
cout << minwindows(t, s);
}
2.字符串的排列
题目要求:窗口中仅包含子串中的元素且数量相同
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
/*题意:判断s2中是否包含s1
1.设置左右指针,把s1的所有字符都放入哈希表中
2.移动右指针,直到windows满足包含needs中的所有元素(即windos==needs)
3.缩小windows,直到windows不再等价于needs,如果有right-left=s1.size()
说明包含了全排列,可以直接return true,反之继续寻找,等到右指针到终点还没有时,就跳出循环,没有找到*/
bool checkInclusion(string s1, string s2)
{
map<char, int> windows, needs;
for (char c : s1)
needs[c]++;
int valid = 0;
int left = 0, right = 0;
while (right < s2.size())
{
char c = s2[right];
right++;
if (needs.count(c))
{
windows[c]++;
if (windows[c] == needs[c])
valid++;
}
while (valid == needs.size()) //全部满足了
{
if (right - left == s1.size())
return true;
char d = s2[left];
left++;
if (needs.count(d))
{
if (windows[d] == needs[d])
valid--;
windows[d]--;
}
}
}
return false;
}
int main()
{
string s1, s2;
s1 = "ab";
s2 = "eidboaoo";
cout << checkInclusion(s1, s2);
return 0;
}
3.找字符串中所有字母的异位词
题目要求:窗口中仅包含子串中的元素且数量相同
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
#include <vector>
using namespace std;
vector<int> findAnagrams(string s, string p)
{
vector <int > ans;
int right = 0;
int left = 0;
map<char, int> windows, needs;
for (char c : p)
needs[c]++;
int valid = 0;
while (right < s.size())
{
char c = s[right];
right++;
if (needs.count(c))
{
windows[c]++;
if (windows[c] == needs[c])
valid++;
}
while (valid == needs.size())
{
if (right - left == p.size())
{
ans.push_back(left);
}
char d = s[left];
left++;
if (needs.count(d))
{
if (windows[d] == needs[d])
valid--;
windows[d]--;
}
}
}
return ans;
}
int main()
{
string s = "abab", p = "ab";
for(int a:findAnagrams(s,p))
cout<<a<<' ';
}
4.不含重复字母的最小子串
题目要求:窗口中不能有重复的字符,且窗口的长度要最大
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
int lengthOfLongestSubstring(string s)
{//给定一个字符串,找出字符串中不包含重复字符的最长子串
map<char, int> windows;
int left = 0, right = 0;
int maxL = -1;
while (right < s.size())
{
char c = s[right];
right++;
windows[c]++;
while (windows[c] > 1)
{
char d = s[left];
left++;
windows[d]--;
}
maxL = max(maxL, right - left);
}
return maxL;
}
int main()
{
string s= "aabccccc";
cout<<lengthOfLongestSubstring(s);
}