不要畏惧滑动窗口,其核心就是,什么时候扩大窗口(扩大一格还是多格),什么时候缩小窗口,什么时候记录答案。滑动窗口对应问题就是找子串,但要注意对于一些存在负数的情况,我们应该仔细审视。

我们可以看到所谓的子串其实顺序,连续性都没有要求,第一个难点就是如何确定这种的边界条件。所以我们需要一个数据结构统计每个字符出现的次数,第二个难点,根据之前的三问,什么时候扩展窗口---当我们的窗口中覆盖了所有的子串所需字符(通过第一个难点解决),什么时候缩窗口---当扩展完后,我们开始缩窗口,直到再缩就不满足覆盖条件时,我们选择记录答案,也就是第三个问题。
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for(char c : t) {
need[c]++;
}//初始化窗口
int left = 0, right = 0;
int valid = 0;//字符种类
int len = INT_MAX;
int start = 0;
while(right < s.size()) {
char c = s[right];
right++;
if(need.find(c) != need.end()) {
window[c]++;
if(need[c] == window[c]) {
valid++;
}
}
while(valid == need.size()) {
if(right - left < len) {
start = left;
len = right -left;
}//记录答案
char d = s[left];
left++;
if(need.find(d) != need.end()) {
if(window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return len == INT_MAX ? "" : s.substr(start,len);
}
};
left 和right才是滑动窗口,window只是配合need的边界条件检查,start和len只是存储答案。我们扩展窗口和缩小窗口都选择while,缩小条件是每种字符串数量都满足即子串已经被完全覆盖。
另外就是要注意判断顺序,增减的顺序。
这道题是比较全面的一道题,后面的滑动窗口,可以大概视为其简化版本。

class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for(const char& c : s1) {
need[c]++;
}
int left = 0, right = 0;
int valid = 0;
while(right < s2.length()) {
char c = s2[right];
right++;
if(need.find(c) != need.end()) {
window[c]++;
if(window[c] == need[c]) {
valid++;
}
}
while(valid == need.size()) {
if((right - left) == s1.length()) {
return true;
}
char d = s2[left];
left++;
if(need.find(d) != need.end()) {
if(window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return false;
}
};
我们来分析一下这道题,将重点放在两道题的对比上,这道题最大的变化是什么?-----从覆盖变成了排列,什么意思呢,就是我们子串的长度是固定的。其他什么都没变。我们来仔细看一下代码,尤其是其变化的部分,第一:start和len没了,因为这两个参数就是为了求子串,但这道题是判断(bool)所以不需要那两个参数,第二:写答案的逻辑,也是我们主要改变的地方,我们可以看到,其改变就是针对子串长度固定这个问题,从记录最小覆盖的子串,变成了是否存在子串排列。如果你真的理解了这些变化,那么距离掌握滑动窗口就很近了。
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

小样,换个马甲我就认不出你了?什么异位词,不就是上一道题中的排列吗
在看不同,返回的不是bool了改成其实索引了。
直接写:
class Solution {
public:
vector<int> findAnagrams(string s2, string s1) {
unordered_map<char, int> need, window;
for(const char& c : s1) {
need[c]++;
}
int left = 0, right = 0;
int valid = 0;
vector<int> res;
while(right < s2.length()) {
char c = s2[right];
right++;
if(need.find(c) != need.end()) {
window[c]++;
if(window[c] == need[c]) {
valid++;
}
}
while(valid == need.size()) {
if((right - left) == s1.length()) {
res.push_back(left);
}
char d = s2[left];
left++;
if(need.find(d) != need.end()) {
if(window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return res;
}
};
如果前两道完全掌握,你甚至连改几行代码都会提前想好,从返回bool改成起始地址了,那只需要改返回逻辑就好了,很容易秒杀。

这道题可以作为练习,稍微有变化。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> window;
int left = 0, right = 0;
int max_len = 0;
while (right < s.length()) {
char c = s[right];
right++;
window[c]++;
// 当窗口中字符 c 的数量大于 1 时,收缩窗口
while (window[c] > 1) {
char d = s[left];
left++;
window[d]--;
}
// 更新最大长度
max_len = max(max_len, right - left);
}
return max_len;
}
};

被折叠的 条评论
为什么被折叠?



