滑动窗口刷题总结
1. 定长滑窗
定长滑窗比较简单,每次都是单个元素的进出,核心就是快速更新窗口内元素的信息。
模板663.子数组最大平均数I
double findMaxAverage(vector<int>& nums, int k) {
int vowel = 0;
double ans = -1e9-1;
int n = nums.size(), l;
for(int r = 0; r < n ; r++){
// 右入
vowel += nums[r];
l = r-k+1;
if(l < 0) continue;
// 更新
ans = max(ans, (double)vowel);
// 左出
vowel -= nums[l];
}
return ans/k;
}
2. 不定长滑窗
2.1 求最长/最大
题目一般包含至多、小于等于等要求
模板2958.最多K个重复元素的最长子数组
思路:枚举右端点r,维护左端点l,当维护结束时,子数组[l,r]是以r为右端点,满足题目要求的最长子数组。
int maxSubarrayLength(vector<int>& nums, int k) {
int ans = 0, l = 0;
map<int, int> cnt;
for(int r = 0; r < nums.size(); r++){
// 右入
cnt[nums[r]]++;
// 左出
while(cnt[nums[r]] > k){
cnt[nums[l]]--;
l++;
}
ans = max(ans, r-l+1);
}
return ans;
}
2.2 求最短/最小
题目一般包含至少、大于等于等要求。本质与求最长没有区别,只是判定条件不同,一般会比最长的判定条件复杂一些。
模板209.长度最小的子数组
思路:枚举右端点r,维护左端点l,当维护结束时,子数组[l,r]是以r为右端点,满足题目要求的最短子数组。
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size(), ans = n+1, l = 0, sum = 0;
for(int r = 0; r < n; r++){
// 右入
sum += nums[r];
// 左出
while(sum - nums[l] >= target) sum -= nums[l++];
if(sum >= target) ans = min(ans, r-l+1);
}
return ans > n ? 0 : ans;
}
2.3 求子数组数量
2.3.1 越长越合法型
题目一般包含至少、大于等于等要求,是2.2求最短/最小子数组的变形,一般会写ans+=l。
模板3325.字符至少出现K次的子字符串I
思路:枚举右端点r,维护左端点l,当维护结束时,子数组[l-1,r]是以r为右端点,满足题目要求的最短子数组,则以0、1、...、l-1为左端点的子数组都满足要求,ans+=l。
int numberOfSubstrings(string s, int k) {
int n = s.size(), l=0, ans = 0;
vector<int> cnt(26);
for(int r = 0; r < n; r++){
// 右入
cnt[s[r]-'a']++;
// 左出
while(cnt[s[r]-'a'] >= k) cnt[s[l++]-'a']--;
ans += l;
}
return ans;
}
2.3.2 越短越合法型
题目一般包含至多、小于等要求,是2.1求最长/最大子数组的变形,一般会写ans+=r-l+1。
模板2302.统计得分小于K的子数组
思路:枚举右端点r,维护左端点l,当维护结束时,子数组[l,r]是以r为右端点,满足题目要求的最长子数组,则以l、l+1、...、r为左端点的子数组都满足要求,ans+=r-l+1。
注意内嵌循环判定条件一般需要加上l <= r,否则容易越界
long long countSubarrays(vector<int>& nums, long long k) {
long long sum = 0, ans = 0;
int l = 0;
for(int r = 0; r < nums.size(); r++){
// 右入
sum += nums[r];
// 左出
while(sum*(r-l+1) >= k && l <= r) sum -= nums[l++];
ans += r-l+1;
}
return ans;
}
2.3.3 恰好等于型
可以视情况转化为越长越合法型或越短越合法型。
转化为越长越合法型模板992.K个不同整数的子数组
int f(vector<int>& nums, int k){
int n = nums.size(), l = 0;
int sum = 0, ans = 0;
vector<int> cnt(n+1);
for(int r = 0; r < n; r++){
sum += cnt[nums[r]] == 0;
cnt[nums[r]]++;
while(sum >= k){
cnt[nums[l]]--;
sum -= cnt[nums[l]] == 0;
l++;
}
ans += l;
}
return ans;
}
int subarraysWithKDistinct(vector<int>& nums, int k) {
// 转化为 越长越合法问题
// 不同整数个数 >= k
return f(nums, k) - f(nums, k+1);
}
越短越合法型模板930.和相同的的二元子数组
int f(vector<int>& nums, int goal){
int ans = 0, n = nums.size();
int sum = 0, l = 0;
for(int r = 0; r < n; r++){
sum += nums[r];
while(sum >= goal && l <= r) sum -= nums[l++];
ans += r-l+1;
}
return ans;
}
int numSubarraysWithSum(vector<int>& nums, int goal) {
// 转化为 越短越合法问题
// 求窗口内和sum <= goal的子数组个数
return f(nums, goal+1)-f(nums, goal);
}
咸鱼翻身失败