滑动窗口
滑动窗口使用前提:
- 连续子数组。
- 有单调性。本题元素均为正数,这意味着只要某个子数组满足题目要求,在该子数组内的更短的子数组同样也满足题目要求。
一、不定长滑动窗口(求最短/最小)
209.长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size(), sum = 0;
int l = 0, r = 0;
int ans = 0x3f3f3f3f;
while (r < n) {
sum += nums[r];
while (sum >= target) {//满足要求就更新 ans,再移动左指针
ans = min(ans, r - l + 1);
sum -= nums[l];
l++;
}
r++;
}
return ans <= n ? ans : 0;
}
};
另一种写法:
(需要保证循环后 \(\rm sum\) 仍然大于 \(\rm target\),但如果循环之前 \(\rm sum < target\) 就不会进入循环,循环后还需要 \(\rm if\) 判断一下是否满足 \(\rm sum \geqslant target\)。)
class Solution {
public:
int minSubArrayLen(int target, vector<int> &nums) {
int n = nums.size(), ans = n + 1, sum = 0, left = 0;
for (int right = 0; right < n; right++) { // 枚举子数组右端点
sum += nums[right];
while (sum - nums[left] >= target) { // 尽量缩小子数组长度
sum -= nums[left++]; // 左端点右移
}
if (sum >= target) {
ans = min(ans, right - left + 1);
}
}
return ans <= n ? ans : 0;
}
};
713.乘积小于K的子数组
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
if (k <= 1) return 0;//由于 nums[i] >= 1,乘积不可能小于1。当 k <= 1 时直接返回 0
int n = nums.size();
int l = 0, mul = 1, ans = 0;
for (int r = 0; r < n; r++) {//枚举右端点,计算以每个点为右端点的子数组个数之和。
mul *= nums[r];
while (mul >= k) {//不符合要求,就把左端点除去
mul /= nums[l];
l++;
}
ans += r - l + 1;
//若[l, r]满足,则[l+1, r], [l+2, r]...[r-1, r]都满足,共有 r - l + 1 个。
}
return ans;
}
};
3.无重复字符的最长字串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size(), ans = 0, l = 0;
unordered_set<char> window; //维护从下标 l 到 r 的字符
for (int r = l; r < n; r++) {
char c = s[r];
//若窗口内已经包含 c,那么再加入一个 c会导致窗口内有重复字符。所以要在加入 c 之前,先移除窗口内的 c
while (window.count(c)) {
window.erase(s[l++]);//缩小窗口
}
window.insert(c);
ans = max(ans, r - l + 1);
}
return ans;
}
};
2958.最多K个重复元素的最长子数组
class Solution {
public:
int maxSubarrayLength(vector<int>& nums, int k) {
int n = nums.size(), ans = 0, l = 0;
unordered_map<int, int> mp;
for (int r = l; r < n; r++) {
mp[nums[r]]++;
while (mp[nums[r]] > k) {
mp[nums[l]]--;
l++;
}
ans = max(ans, r - l + 1);
}
return ans;
}
};
2730.找到最长的半重复子字符串
class Solution {
public:
int longestSemiRepetitiveSubstring(string s) {
int n = s.size(), ans = 0, l = 0, cnt = 0;
if (n == 1) return 1;//长度为1时不进入循环,直接return
for (int r = 1; r < n; r++) {
if (s[r - 1] == s[r]) {
cnt++;
if (cnt >= 2) {
l++;//如果当前已经s[l] == s[l-1]就不动了,l++是确保其进入新的滑窗
while (l < n && s[l] != s[l - 1]) l++;//不断移动左指针直到把一对相同的字符破除
cnt = 1;//更新cnt
}
}
ans = max(ans, r - l + 1);
}
return ans;
}
};
2779.数组的最大美丽值


class Solution {
public:
int maximumBeauty(vector<int>& nums, int k) {
int n = nums.size();
sort(nums.begin(), nums.end());
int l = 0, ans = 0;
for (int r = l; r < n; r++) {
if (nums[r] - nums[l] > 2 * k) l++;
ans = max(ans, r - l + 1);
}
return ans;
}
};
1004.最大连续1的个数
其实就是要在窗口内 \(0\) 的个数 \(\rm cnt \leqslant k\) 的条件下寻找区间长度最长的窗口。
一旦 \(\rm cnt > k\) 就不断移动左指针,直到 \(\rm cnt \leqslant k\).
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int ans = 0, n = nums.size(), cnt = 0;
for (int l = 0, r = 0; r < n; r++) {
cnt += 1 - nums[r];
while (cnt > k) {
cnt -= 1 - nums[l];
l++;
}
ans = max(ans, r - l + 1);
}
return ans;
}
};
2962.统计最大元素出现至少K次的子数组
若数组元素等于最大元素就把计数器++,若计数器数量 \(\geqslant k\) 就把左指针++直到窗口内最大元素数量小于 \(k\)。由于在 \(\rm [0,left-1]\) 时窗口内最大元素数量都是 \(\geqslant k\) 的,所以 \(\rm ans\) += \(\rm left\)。
class Solution {
public:
long long countSubarrays(vector<int>& nums, int k) {
int n = nums.size();
int m = *max_element(nums.begin(), nums.end());
int cnt = 0, l = 0;
long long ans = 0;
for (int r = 0; r < n; r++) {
if (nums[r] == m) {
cnt++;
}
while (cnt >= k) {
cnt -= (nums[l] == m);
l++;
}
ans += l;
}
return ans;
}
};
2302.统计得分小于K的子数组数目
独立做出来了qwq,虽然有猜的成分~
固定子数组右端点 right,寻找满足条件的左端点 left,如果 sum(left...right) * (right - left + 1) < k,那么当 i > left 时,一定有 sum(i...right) * (right - i + 1) < k。
也就是,固定子数组右端点,寻找子数组分数 < k 的最长的那个子数组 [left...right],那么比它短的所有子数组分数一定 < k,所以子数组个数是 right - left + 1 ~
class Solution {
public:
long long countSubarrays(vector<int>& nums, long long k) {
int n = nums.size(), l = 0;
long long sum = 0;
long long ans = 0;
for (int r = 0; r < n; r++) {
int len = r - l + 1;
sum += nums[r];
long long x = 1ll * len * sum;
while (x >= k) {
sum -= nums[l];
len--;
l++;
x = 1ll * sum * len;
}
ans += r - l + 1;
}
return ans;
}
};
1658.将x减到0的最小操作数
正难则反。
原问题等价为,从原数组中移除一个最长的子数组,使得剩余元素之和为 \(x\),也即要从原数组中找一个最长的子数组满足元素之和为 \(s-x\),其中 \(s\) 为原数组的元素之和。
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int n = nums.size(), l = 0, ans = -1;
int m = accumulate(nums.begin(), nums.end(), 0);
m -= x;
int sum = 0;
for (int r = 0; r < n; r++) {
sum += nums[r];
while (l < n && sum > m) {
sum -= nums[l++];
}
if (sum == m) ans = max(ans, r - l + 1);
}
if (ans >= 0) return n - ans;
return -1;
}
};
76.最小覆盖字串
class Solution {
public:
bool is_covered(int cnt_s[], int cnt_t[]) {//涵盖就是对于所有字符而言,在s中的数量都要大于等于在t中的数量
for (int i = 'A'; i <= 'Z'; i++) {
if (cnt_s[i] < cnt_t[i]) return false;
}
for (int i = 'a'; i <= 'z'; i++) {
if (cnt_s[i] < cnt_t[i]) return false;
}
return true;
}
string minWindow(string s, string t) {
int cnt_t[128]{}, cnt_s[128]{};//数组模拟哈希(更快)。注意这里若使用unordered_map,容器作为形参传入时需加引用,否则会拷贝一份,易TLE。
int m = s.length();
for (auto c : t) cnt_t[c]++;
int l = 0;
int ans_l = -1, ans_r = m;//ans设置为-1,若最后仍然是负数说明未被更新,返回空串。
for (int r = 0; r < m; r++) { // 移动子串右端点
cnt_s[s[r]]++; // 右端点字母移入子串
while (is_covered(cnt_s, cnt_t)) { // 涵盖
if (r - l < ans_r - ans_l) { // 找到更短的子串,满足条件就更新
ans_l = l; // 记录此时的左右端点
ans_r = r;
}
cnt_s[s[l++]]--; // 左指针移动到不涵盖为止
}
}
return ans_l < 0 ? "" : s.substr(ans_l, ans_r - ans_l + 1);
}
};

浙公网安备 33010602011771号