滑动窗口算法是在给定特定窗口大小(当然也可以是动态可变窗口)的数组或者字符串上进行操作的算法,该算法主要的用途就是在于将嵌套循环时间复杂度的效率优化成为线性时间复杂度。简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。
从字面意思上理解的话:
滑动: 说明这个窗口是移动的,也就是移动是按照一定方向来的。
窗口: 窗口大小并不是固定的,可以不断扩容直到满足一定的条件;也可以不断缩小,直到找到一个满足条件的最小窗口;当然也可以是固定大小。
一般来讲,滑动窗口算法需要借助两个指针left与right来完
/* 滑动窗口算法框架 */
void slidingWindow(string s) {
// 用合适的数据结构记录窗口中的数据,根据具体场景变通
// 比如说,我想记录窗口中元素出现的次数,就用 map
// 我想记录窗口中的元素和,就用 int
unordered_map<char, int> window;
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
window.add(c)
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (left < right && window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
window.remove(d)
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
76. 最小覆盖子串
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for (auto c : t) {
need[c]++;
}
int left = 0, right = 0;
int valid = 0;
int start = 0, len = INT_MAX;
while (right < s.size()) {
// 扩大窗口
char c = s[right];
right++;
// 更新满足窗口覆盖需要的相关条件
if (need.count(c)) {
window[c]++;
if (need[c] == window[c]) {
valid++;
}
}
// 缩小窗口,并更新覆盖条件
while (valid == need.size()) {
// result处理,判断是否更新,便于返回
if (right - left < len) {
start = left;
len = right - left;
}
// 缩小窗口
char d = s[left];
left++;
if (need.count(d)) {
if (need[d] == window[d]) {
valid--;
}
window[d]--;
}
}
}
return len == INT_MAX ? "" : s.substr(start, len);
}
};
209. 长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0, right = 0;
int sum = 0;
int len = INT_MAX;
while (right < nums.size()) {
sum += nums[right];
right++;
while (sum >= target) {
if (right - left < len) {
len = right - left;
}
sum -= nums[left];
left++;
}
}
return len == INT_MAX ? 0 : len;
}
};
567. 字符串的排列
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for (auto c : s1) {
need[c]++;
}
int left = 0, right = 0;
int valid = 0;
while (right < s2.size()) {
// 扩大窗口
char c = s2[right];
right++;
// 更新判断条件
if (need.count(c)) {
window[c]++;
if (need[c] == window[c]) {
valid++;
}
}
// 缩小窗口时机,固定长度的窗口
if (right - left == s1.size()) {
// 有效条件
if (valid == need.size()) {
return true;
}
// 缩小窗口
char d = s2[left];
left++;
// 更新判断条件
if (need.count(d)) {
if (need[d] == window[d]) {
valid--;
}
window[d]--;
}
}
}
return false;
}
};
438. 找到字符串中所有字母异位词
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> need, window;
for (char c : p) need[c]++;
int left = 0, right = 0, valid = 0;
vector<int> res;
while (right < s.size()) {
char c = s[right];
right++;
if (need.count(c)) {
window[c]++;
if (window[c] == need[c]) {
valid++;
}
}
while (right - left >= p.size()) {
if (valid == need.size()) {
res.push_back(left);
}
char d = s[left];
left++;
if (need.count(d)) {
if (window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return res;
}
};
3. 无重复字符的最长子串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> window;
int left = 0, right = 0, res = 0;
while (right < s.size()) {
char c = s[right];
right++;
window[c]++;
while (window[c] > 1) {
char d = s[left];
left++;
window[d]--;
}
res = max(res, right - left);
}
return res;
}
};