概念
滑动窗口是在数组上通过双指针同向移动而解决问题的方法。这样的问题我们不必为它们专门命名一个名字,它们的解法其实是很自然的。
滑动窗口通常是暴力解法的优化,掌握这一类问题最好的办法就是练习,然后思考清楚为什么可以使用滑动窗口。
209. 长度最小的子数组
该题是最基本的滑动窗口问题,我们可以用两个指针 l l l和 r r r表示窗口的左右段,然后让指针 r r r持续向右移动,同时用一个变量 s u m sum sum记录 [ l , r ] [l,r] [l,r]的区间和,若 s u m sum sum满足条件,则记录 l l l和 r r r的差作为答案,并移动指针 l l l,代码如下
class Solution {
public:
int minSubArrayLen(int target, vector<int> &nums) {
int l = 0, r = 0, res = INT_MAX, sum = 0;
while (r < nums.size()) {
sum += nums[r];
while (sum >= target) {
res = min(res, r - l + 1);
sum -= nums[l];
l++;
}
r++;
}
return res == INT_MAX ? 0 : res;
}
};
713. 乘积小于K的子数组
这道题的解法与上道题非常相似,但求的不再是区间长度而是连续子区间数量。
对于子区间数量,我们可以发现,若 [ l , r ] [l,r] [l,r]是一个符合条件的窗口,则 [ l + i , r ] , ( 0 < i ≤ r − l ) [l+i,r],(0<i≤r-l) [l+i,r],(0<i≤r−l)也必然是符合条件的窗口
即对于任何符合条件的区间 [ l , r ] [l,r] [l,r],以 r r r结尾的子集个数永远是 r − l + 1 r-l+1 r−l+1,因此我们要在每次更新 r r r之前为答案加上 r − l + 1 r-l+1 r−l+1,代码如下
class Solution {
public:
int numSubarrayProductLessThanK(vector<int> &nums, int k) {
int l = 0, r = 0, sum = 1, ans = 0;
if (k <= 1) return 0;
while (r < nums.size()) {
sum *= nums[r];
while (sum >= k) {
sum /= nums[l];
l++;
}
ans += r - l + 1;
r++;
}
return ans;
}
};
3. 无重复字符的最长子串
与前面的连续子序列不同的是,这道题要求的是连续子串的长度,因此我们需要修改一下判定条件,若窗口区间内出现重复字符,则更新 l l l,代码如下
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int l = 0, r = 0, ans = 0;
map<char, int> cnt;
while (r < s.length()) {
cnt[s[r]]++;
while (cnt[s[r]] > 1) {
cnt[s[l]]--;
l++;
}
ans = max(ans, r - l + 1);
r++;
}
return ans;
}
};
438. 找到字符串中所有字母异位词
这道题解法与上一题大同小异,是把字符的判据改为了字符串的比较。同时,该题的窗口长度始终保持不变,为 p p p的长度
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans, target(26, 0), sum(26, 0);
if (s.length() < p.length()) return ans;
for (int i = 0; i < p.length(); i++) target[(int) (p[i] - 'a')]++, sum[(int) (s[i] - 'a')]++;
if (sum == target) ans.push_back(0);
int l = 0, r = p.length();
while (r < s.length()) {
sum[(int) (s[l++] - 'a')]--;
sum[(int) (s[r++] - 'a')]++;
if (sum == target)
ans.push_back(l);
}
return ans;
}
};