提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本篇主要是讲的是滑动窗口的两种类型。
定义:滑动窗口
什么是滑动窗口呢?就像它的名字一样,是一个滑动的窗口。
一、定长滑动窗口
对于定长滑动窗口,总体来说是简单一些的,就是由于长度定了,才使得接下来解题更方便。
1.定长子串中元音的最大数目
题目传送门:定长子串中元音的最大数目
解决代码:
class Solution {
public:
int maxVowels(string s, int k) {
int n = s.size(); // 字符串长度
int ans = 0; // 当前窗口内元音字母的数量
unordered_map<char, int> m; // 用于快速判断字符是否为元音的哈希表
m['a'] = 1;
m['e'] = 1;
m['i'] = 1;
m['o'] = 1;
m['u'] = 1;
deque<char> p; // 存储当前窗口中的字符(实际上可以优化掉)
int max1 = 0; // 记录最大元音数量
// 初始化第一个窗口(前k个字符)
for(int i = 0; i < k; i++) {
p.push_back(s[i]); // 将字符加入窗口
if(m.count(s[i])) // 如果是元音字母
ans++; // 计数器加1
max1 = max(ans, max1); // 更新最大值
}
// 滑动窗口:从第k个字符开始,逐个移动窗口
for(int i = k; i < n; i++) {
if(m.count(p.front())) // 如果窗口最左侧字符是元音
ans--; // 移除该字符后计数器减1
p.pop_front(); // 移除窗口最左侧字符
p.push_back(s[i]); // 添加新字符到窗口右侧
if(m.count(s[i])) // 如果新字符是元音
ans++; // 计数器加1
max1 = max(ans, max1); // 更新最大值
}
return max1; // 返回最大元音数量
}
};
对于这一题关键就是维持窗口的大小,同时还要注意窗口中的值的变化,对于结果的影响。
2.子数组最大平均数1
题目传送门:子数组最大平均数1
解决代码:
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int n = nums.size(); // 数组长度
deque<double> p; // 存储当前窗口中的元素(可优化)
double sum = 0; // 当前窗口的元素和
double ans = -1e5; // 最大平均值(初始化为一个很小的值)
// 预处理:先将前k-1个元素加入窗口
for(int i = 0; i < k-1; i++) {
p.push_back(nums[i]); // 将元素加入队列
sum += nums[i]; // 累加元素和
}
// 滑动窗口:从第k-1个元素开始,逐个移动窗口
for(int i = k-1; i < n; i++) {
p.push_back(nums[i]); // 添加新元素到窗口右侧
sum += nums[i]; // 更新窗口和
// 计算当前窗口的平均值并更新最大值
ans = max(ans, sum / k);
// 移除窗口最左侧元素,为下一次滑动做准备
sum -= p.front(); // 减去左侧元素的值
p.pop_front(); // 移除左侧元素
}
return ans; // 返回最大平均值
}
};
其实这一题与上面的题差不多。
3.大小为K且平均值大于等于阈值的子数组数目
题目传送门:大小为K且平均值大于等于阈值的子数组数目
解决代码:
class Solution {
public:
int numOfSubarrays(vector<int>& arr, int k, int threshold) {
int n = arr.size(); // 数组长度
double sum = 0; // 当前窗口的元素和
int ans = 0; // 符合条件的子数组数量
deque<double> p; // 存储窗口元素(可优化掉)
// 预处理:计算前k-1个元素的和
for(int i = 0; i < k-1; i++) {
sum += arr[i];
p.push_back(arr[i]); // 将元素加入队列(后续会被移除)
}
// 滑动窗口处理:从第k-1个元素开始遍历到数组末尾
for(int i = k-1; i < n; i++) {
sum += arr[i]; // 窗口右侧新增元素
p.push_back(arr[i]); // 将元素加入队列(可优化)
// 判断当前窗口平均值是否达标
if(sum / k >= threshold) {
ans++; // 达标则计数器加1
}
// 窗口左边界右移一位
sum -= p.front(); // 减去窗口左侧元素
p.pop_front(); // 移除队列头部元素(可优化)
}
return ans; // 返回符合条件的子数组数量
}
};
二、不定长滑动窗口
对这一类题目,写这一些题目时,我更倾向于嵌套大佬的模板
对于这一类,通常是把问题分解成两个状态,以此来进行窗口的变化,
1.无重复的最长字符串
题目传送门:无重复的最长字符串
解决代码:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n=s.size();
int ans=0;
unordered_map<char,int>m;//来记录是否出现重复字符
for(int left=0,right=0;right<n;right++)
{
m[s[right]]++;//每次都存一下字符出现的次数
while(m[s[right]]>1)//如果最右边的字符出现多次进行循环
{
m[s[left]]--;//最左边的下标向右移动
left++;
}
ans=max(ans,right-left+1);//每次都判断一下ans的最大值
}
return ans;
}
};
2.最长和谐子序列
题目传送门:最长和谐子序列
由于题目所说并不是连续子序列,所以可以先进行排序之后再进行操作
解决代码:
class Solution {
public:
int findLHS(vector<int>& nums) {
int n=nums.size();
int ans=0;
sort(nums.begin(),nums.end());//先进行排序,之后寻找满足条件的子数组就简单的一些
for(int left=0,right=0;right<n;right++)
{
while((nums[right]-nums[left])>1)//其最大值与最下值的差值如果大于1就将左下标进行向右移动
{
left++;
}
if(nums[right]-nums[left]==1)//如果满足条件就进行答案的更新。
ans=max(right-left+1,ans);
}
return ans;
}
};
3.乘积小于k的子数组
题目传送门:乘积小于k的子数组
对于这一题同样套用上面的模板,其中的条件也要进行改变一下,其中要主义left要小于等于right
解决代码:
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int n=nums.size();
int ans=0;
int sum=1;
for(int left=0,right=0;right<n;right++)
{
sum*=nums[right];
while(sum>=k&&left<=right)//满足这些条件时,将左下标向右移动,同时也要将数据移除。
{
sum=sum/nums[left];
left++;
}
ans+=right-left+1;//至于为什么该操作可以计算出结果,请看下面的解释
}
return ans;
}
};
总结
当然,经过这些题目的练习,还是只能做一些浅显的题目,对于那些条件特别复杂的,还是很难理清,希望,经过接下来的练习可以更精进一些。