1.定长子串中元音的最大数目
题目
解析
1.解析
- 定长滑动窗口三步走:
- 1.指针 i 不断右移,直到窗口长度为 K;
- 2.更新窗口,一般为最大值 / 最小值;
- 3.移动窗口,检查出窗口元素,改变相应的变量;
- 时间复杂度:O(n);空间复杂度:O(1);
代码
class Solution {
public:
int maxVowels(string s, int k) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int n = s.size();
int ans = 0;
int vowel = 0; // 当前元音个数
for(int i = 0;i < n;i ++){
// 进入窗口
if(s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u'){
vowel ++;
}
// 窗口长度不够 k
// 当 i = k - 1 后,后面的窗口就固定长度了
if(i < k - 1) continue;
// 每移动一次,更新一次
ans = max(ans,vowel);
// 离开窗口
char out = s[i - k + 1];
if(out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u'){
vowel --;
}
}
return ans;
}
};
2.子数组最大平均数
题目
解析
1.解析
- 思路没什么区别,就是这题注意要用 double 返回结果;
代码
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int ans = -1e8;
int sum = 0;
for(int i = 0;i < nums.size();i ++){
sum += nums[i];
if(i < k - 1) continue;
ans = max(ans,sum);
sum -= nums[i - k + 1];
}
return (double) ans / k;
}
};
3.大小为K且平均值大于等于阈值的子数组数
题目
解析
1.解析
- 没啥说的,一样的思路
代码
class Solution {
public:
int numOfSubarrays(vector<int>& arr, int k, int threshold) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int ans = 0;
int aver;
int sum = 0;
for(int i = 0;i < arr.size();i ++){
sum += arr[i];
if(i < k - 1) continue;
aver = sum / k;
if(aver >= threshold) ans ++;
sum -= arr[i - k + 1];
}
return ans;
}
};
4.半径为 K 的子数组平均值
题目
解析
1.解析
- 题目要求没有平均值的地方为-1,所以直接开始就设定一个全为-1的数组;
- 我们把左右半径内的子数组作为滑动窗口,i 计算的为右边界,答案要赋值给 i - K;
- 这题需要注意下标的一一对应;
代码
class Solution {
public:
vector<int> getAverages(vector<int>& nums, int k) {
// 时间复杂度;O(n)
// 空间复杂度:O(1)
int n = nums.size();
long long sum = 0;
vector<int> ans(n,-1); // 提前设置为-1,后面只计算存在平均值的位置
for(int i = 0;i < n;i ++){
sum += nums[i];
if(i < 2 * k) continue;
ans[i - k] = sum / (2 * k + 1);
sum -= nums[i - 2 * k];
}
return ans;
}
};
5.得到 K 个黑块的最少涂色次数
题目
解析
1.解析
- 没啥说的,一样的思路
代码
class Solution {
public:
int minimumRecolors(string blocks, int k) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int n = blocks.size();
int cnt = 0;
int ans = k + 1;
for(int i = 0;i < n;i ++){
if(blocks[i] == 'W') cnt ++;
if(i < k - 1) continue;
ans = min(ans,cnt);
if(blocks[i - k + 1] == 'W') cnt --;
}
return ans;
}
};
6.几乎唯一子数组的最大和
题目
解析
1.解析
- 核心:用哈希表存储每个滑动窗口内元素的个数,其大小为不同元素的个数;
- 当哈希表个数大于等于 m 时,说明窗口内不同元素个数大于等于 m,此时可以更新长度;
- 最后去除左边元素的同时,还要给对应哈希表的值减 1;
- 如果此时该数的哈希表值为 0,则把它从哈希表中删除,防止占位;
- 时间复杂度:O(n);空间复杂度:O(k);
代码
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
// 时间复杂度:O(n)
// 空间复杂度;O(k)
int n = nums.size();
long long ans = 0;
long long sum = 0;
unordered_map<int,int> cnt;
for(int i = 0;i < n;i ++){
sum += nums[i];
cnt[nums[i]] ++;
if(i < k - 1) continue;
// 哈希表的大小,即当前窗口不同元素的个数
if(cnt.size() >= m) ans = max(ans,sum);
int s = i - k + 1;
sum -= nums[s];
// 如果减去左边的元素对应哈希表值变为0,去除该键值对
// 如果不去除,该元素还是占了哈希表的一个位置
cnt[nums[s]] --;
if(cnt[nums[s]] == 0) cnt.erase(nums[s]);
}
return ans;
}
};
7.长度为 K 子数组中的最大和
题目
解析
1.解析
- 思路和上一题一模一样,没什么说的;
代码
class Solution {
public:
long long maximumSubarraySum(vector<int>& nums, int k) {
// 时间复杂度:O(n)
// 空间复杂度:O(k)
long long ans = 0;
long long sum = 0;
unordered_map<int,int> cnt;
for(int i = 0;i < nums.size();i ++){
sum += nums[i];
cnt[nums[i]] ++;
if(i < k - 1) continue;
if(cnt.size() == k) ans = max(ans,sum);
int s = i - k + 1;
sum -= nums[s];
cnt[nums[s]] --;
if(cnt[nums[s]] == 0) cnt.erase(nums[s]);
}
return ans;
}
};
8.爱生气的书店老板
题目
解析
1.解析
- 先计算所有满意的顾客,不满意的顾客最大值用滑动窗口解决,这里 K = minutes;
- 注意点:移动窗口时,若左边界为不满意的顾客才需要减去;
代码
class Solution {
public:
int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int n = customers.size();
int sum = 0;
int max_sum = 0;
int ans = 0;
// 满意的顾客
for(int i = 0;i < n;i ++){
if(grumpy[i] == 0) ans += customers[i];
}
// 不满意的顾客在窗口内最大数
for(int i = 0;i < n;i ++){
if(grumpy[i] == 1) sum += customers[i];
if(i < minutes - 1) continue;
max_sum = max(max_sum,sum);
if(grumpy[i - minutes + 1] == 1) sum -= customers[i - minutes + 1];
}
return ans + max_sum;
}
};
9.可获得的最大点数
题目
解析
1.解析
- 本题运用了反向思维,题目求两端最大点数,我们可以转化为求中间滑动窗口点数和的最小值;
- ans 设置为了最大值,INT_MAX;
代码
class Solution {
public:
int maxScore(vector<int>& cardPoints, int k) {
// 时间复杂度:O(n)
// 空间复杂度:O(1)
int n = cardPoints.size();
int ans = INT_MAX; // 最大值
int sum = 0;
// 记录所有点数之和
int cnt = 0;
for(int i = 0;i < n;i ++) cnt += cardPoints[i];
// 全拿的情况单独讨论
if(k == n) ans = 0;
// 反向思维,讨论其余卡牌和最小的情况
for(int i = 0;i < n;i ++){
sum += cardPoints[i];
if(i < n - k - 1) continue;
ans = min(ans,sum);
sum -= cardPoints[i - n + k + 1];
}
return cnt - ans;
}
};
10.总结
定长滑动窗口有以下几步:
- 1.移动指针 i ,直到窗口长度变为 K,在过程中记录某些变量的值;
- 2.根据题目条件,更新结果取值;
- 3.移动窗口,即去除左边第一个数,并根据该值去更新某些变量;
- 注意点:移动窗口过程中,下标的变化;