滑动窗口模板
/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {//左闭右开区间
// c 是将移⼊窗⼝的字符
char c = s[right];
// 右移窗⼝
right++;
// 进⾏窗⼝内数据的⼀系列更新
...
// 判断左侧窗⼝是否要收缩
while (window needs shrink) {
// d 是将移出窗⼝的字符
char d = s[left];
// 左移窗⼝
left++;
// 进⾏窗⼝内数据的⼀系列更新
...
}
}
}
76. 最小覆盖子串
在做滑动窗口问题时要明确以下几个问题
- 什么时候停止窗口扩张,哪些参数变化
- 什么时候停止窗口收缩,哪些参数变化
- 是在扩张时更新结果还是收缩时更新结果
对于本题而言,当窗口的字符包含t中的字符时停止扩张,当窗口的字符没有包含t中所有字符时停止收缩,因为这是求最小子串,所以应该是在收缩时更新结果。
准备两个哈希表window,need,need统计t中的词频,window统计s字符串包含t中字符的词频
例如 s=“aabcc”,t=“aa”,window只需要统计a的词频就好了,只要窗口中a的词频>=2就说明找到了一个子串
再准备一个变量valid用来判断是否停止扩张或者收缩、
补充:need.count (c)
//查找以c为键的个数
need[c]:如果没有以c为键的键值对,就会自动创建一个
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int> window,need;//window只需要统计need中字符的词频
int left=0;
int right=0;//[)区间,所以初始时窗口为空
int start=0;//子串的开始位置
int len=INT_MAX;//子串的长度
int valid=0;//窗口中符合条件的字符(字符是t中的每个字符)的个数,当valid==need的长度时就说明找到了一个子串
string re;//保存最终结果
for(char c:t){
need[c]++;//统计t中各字符的个数
}
while(right<s.size()){
char c=s[right];//预进入窗口的字符
right++;//窗口右移
if(need.count(c)){//当t中含有c字符时更新window
window[c]++;
if(window[c]==need[c]){//c字符的个数满足条件
valid++;
}
}
while(valid==need.size()){//找到了一个子串,need.size()就是t中的字符数
if(right-left<len){//更新最小子串
start=left;
len=right-left;
}
char d=s[left];
left++;//窗口右移
if(need.count(d)){//移出窗口的字符是符合条件的字符
if(window[d]==need[d]){//窗口中d字符个数等于need中d的个数
valid--;
}
window[d]--;
}
}
}
re=len==INT_MAX?"":s.substr(start,len);
return re;
}
};
567. 字符串的排列
判断s2中是否有s1的某个全排列,所以就是找一个包含s1的某个全排列的窗口,如果这个窗口的大小就是s1的大小,直接返回true。
详细步骤:
开始,不断向右扩张,直到valid==need.size()
即找到包含s1中所有字符的窗口,然后再进行收缩,收缩前判断窗口的大小是否等于s1的大小,若是,就直接返回true,否则进行收缩
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char,int>window,need;
int valid=0;
int left=0;
int right=0;
for(char c:s1){
need[c]++;
}
while(right<s2.size()){
char in=s2[right];
right++;
if(need.count(in)){
window[in]++;
if(window[in]==need[in]){
valid++;
}
}
while(valid==need.size()){
if(right-left==s1.size()){//已找到s1的某个全排列
return true;
}
char out=s2[left];
left++;
if(need.count(out)){
window[out]--;
if(window[out]<need[out]){
valid--;
}
}
}
}
return false;
}
};
和上一题一样,也是找全排列的窗口
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char,int>window,need;
int left=0;
int right=0;
int valid=0;
vector<int>re;
for(char c:p){
need[c]++;
}
while(right<s.size()){
char in=s[right];
right++;
if(need.count(in)){
window[in]++;
if(window[in]==need[in]){
valid++;
}
}
while(valid==need.size()){
if(right-left==p.size()){//找到一个异位词
re.push_back(left);
}
char out=s[left];
left++;
if(need.count(out)){
window[out]--;
if(window[out]<need[out]){
valid--;
}
}
}
}
return re;
}
};
扩张条件:只要当前窗口没有重复字符就扩张
收缩条件:正好与扩张条件相反,有重复字符就收缩
收集结果的时机:扩张时
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char ,int>window;
int left=0;
int right=0;
int minLen=INT_MIN;
while(right<s.size()){
char c=s[right];
right++;
window[c]++;
if(window[c]<=1){//要保证当前窗口没有重复值时才可以收集结果
minLen=minLen>right-left?minLen:right-left;
}
while(window[c]>1){//收缩
char d=s[left];
left++;
window[d]--;
}
}
return minLen==INT_MIN?0:minLen;
}
};
总结
模板固然好用,但是有时候可以要具体问题具体分析,像最后这一题,如果硬套模板反而更加复杂了,总之做滑动窗口题目时主要分析:扩张的条件,收缩的条件,收集结果的时机。