1.定长子串中元音的最大数目
1456. 定长子串中元音的最大数目 - 力扣(LeetCode)
从这道题目,我们学习了定长滑窗的基本思想
我们要在一串数里面,遍历全部长度为k的子串,来判断元音字母个数
举个例子:长度为8的数组,其中所有长度为3的子串,最多包含元音字母个数
-
最简单的想法就是暴力枚举,按索引,0 1 2, 1 2 3,2 3 4……,每次都计算它们的原因字母个数(变量vowel),设立一个标志变量ans,vowel>ans就更新它的值,最后ans就是答案
但是我们会看到在计算时,0 1 2到1 2 3中,1和2没有改变,我们却重复计算了一遍,怎样优化呢?
-
只需要进行 移除0和添加3 的两个操作,012和123都叫窗口(定长为3)
我们从012到123,就是0出窗口,3进窗口(始终长度为3,窗口前移)
这就是定长滑窗
依旧以上方例子,窗口长度为3,我们一开始就计算下标0 1的元音个数,因为这样方便我们循环(2进窗口,0出窗口的操作)
示例 1:s=abciiidef, k=3。
- 首先统计前 k−1=2 个字母的元音个数,这有 1 个。
- s[2]=c 进入窗口,此时找到了第一个长为 k 的子串 abc,现在元音个数有 1 个,更新答案最大值。然后 s[0]=a 离开窗口,现在元音个数有 0 个。
- s[3]=i 进入窗口,此时找到了第二个长为 k 的子串 bci,现在元音个数有 1 个,更新答案最大值。然后 s[1]=b 离开窗口,现在元音个数有 1 个。
- ……
这就是第一版代码,但是我们可以看到两遍for循环都有相同的isVowel判断,所以我们可以合并成一个for循环(使用continue跳出)
int isVowel(char s){
if(s=='a'||s=='e'||s=='i'||s=='o'||s=='u'){
return 1;
}
return 0;
}
int max(int x,int y){
if(x>y) return x;
return y;
}
int maxVowels(char* s, int k) {
int ans=0;
int vowel=0;
for(int i=0;i<k-1;i++){
if(isVowel(s[i])){
vowel++;
}
}
for(int i=k-1;s[i]!='\0';i++){
//1.入滑窗
if(isVowel(s[i])){
vowel++;
}
//2.更新最大值
ans=max(ans,vowel);
//优化:最大元音字母数就为滑窗长度可以直接退出
if(ans==k) return k;
//3.出滑窗
if(isVowel(s[i+1-k])){
vowel--;
}
}
return ans;
}
第二版优化代码,新鲜出炉
int isVowel(char s){
if(s=='a'||s=='e'||s=='i'||s=='o'||s=='u'){
return 1;
}
return 0;
}
int max(int x,int y){
if(x>y) return x;
return y;
}
int maxVowels(char* s, int k) {
int ans=0;
int vowel=0;
for(int i=0;s[i]!='\0';i++){
//1.入滑窗
if(isVowel(s[i])){
vowel++;
}
int left=i-k+1;//窗口左端点
if(left<0) continue;//窗口大小不足k,未形成第一个窗口
//2.更新最大值
ans=max(ans,vowel);
//优化:最大元音字母数就为滑窗长度可以直接退出
if(ans==k) return k;
//3.出滑窗
if(isVowel(s[left])){
vowel--;
}
}
return ans;
}
定长滑窗模版
现在总结一下定长滑窗套路1:
窗口右端点在i时,由于窗口长度为k,窗口左端点为i-k+1
滑动时有三步操作:
- 入:下标为 i i i元素进入窗口,统计数量。如果左端点 i − k + 1 < 0 i-k+1<0 i−k+1<0,则尚未形成第一个窗口,重复第一步。
- 更新:更新答案。一般是更新最大/小值
- 出:下标为 i − k + 1 i-k+1 i−k+1元素离开窗口,统计数量(为下一个循环做准备)
2.子数组最大平均数I
643. 子数组最大平均数 I - 力扣(LeetCode)
double max(double x,double y){
if(x>y) return x;
return y;
}
double findMaxAverage(int* nums, int numsSize, int k) {
double aver=0.0;
double ans=-1.0e10;
double sum=0.0;
for(int i=0;i<k-1;i++){
sum+=nums[i];
}
for(int i=k-1;i<numsSize;i++){
//入
sum+=nums[i];
//更新
aver=sum/k;
ans=max(aver,ans);
//出
sum-=nums[i+1-k];
}
return ans;
}
3.大小为K且平均值大于等于阈值的子数组数目
1343. 大小为 K 且平均值大于等于阈值的子数组数目 - 力扣(LeetCode)
int numOfSubarrays(int* arr, int arrSize, int k, int threshold) {
int sum=0,aver=0,count=0;
for(int i=0;i<k-1;i++){
sum+=arr[i];
}
for(int i=k-1;i<arrSize;i++){
//入
sum+=arr[i];
//更新
aver=sum/k;
if(aver>=threshold) count++;
//出
sum-=arr[i+1-k];
}
return count;
}
4.半径为k的子数组平均值
2090. 半径为 k 的子数组平均值 - 力扣(LeetCode)
窗口长度为 2 k + 1 2k+1 2k+1
- 入:下标为 i i i的元素进入窗口,窗口元素和sum增加。如果 i < 2 k i<2k i<2k,就重复第一步
- 更新:记录答案
avgs[i-k]=sum/(2*k+1) - 出:下标为 i − 2 k i-2k i−2k的元素离开窗口,sum减少
注意:窗口元素和sum会超过32位最大整数int,所以要用long
int* getAverages(int* nums, int numsSize, int k, int* returnSize) {
int*avgs=(int*)malloc(numsSize*sizeof(int));
*returnSize=numsSize;
long sum=0;
for(int i=0;i<numsSize;i++){
avgs[i]=-1;
//入
sum+=nums[i];
if(i<2*k){
continue;
}
//更新
avgs[i-k]=sum/(2*k+1);
//出
sum-=nums[i-2*k];
}
return avgs;
}
5.得到k个黑块的最少涂色次数
2379. 得到 K 个黑块的最少涂色次数 - 力扣(LeetCode)
这道题对于我来说最难的是想到定长滑窗的做法,只要告诉你是定长滑窗,就很容易从滑窗长度为k入手,步步解析。
我们想求的最少操作次数等价于连续k个块的最少白块数量
int min(int x,int y){
if(x<y) return x;
return y;
}
int minimumRecolors(char* blocks, int k) {
int num=0;//操作次数(白块个数)
int result=100;
for(int i=0;blocks[i]!='\0';i++){
if(blocks[i]=='W'){
num++;
}
if(i<k-1) continue;
result=min(result,num);
if(blocks[i+1-k]=='W'){
num--;
}
}
return result;
}
6.几乎唯一子数组的最大和
2841. 几乎唯一子数组的最大和 - 力扣(LeetCode)
这道题的难点在于如何表示数组中互不相同的元素的个数,我们采用哈希表,来统计每个元素出现的次数
-
入:元素 x=nums[i] 进入窗口,把 x 加到元素和 s 中,把 x 加到哈希表中(统计 x 的出现次数)。如果 i<k−1 则重复第一步。
-
更新:如果哈希表的大小 ≥m,用 s 更新答案的最大值。
-
出:元素 x=nums[i−k+1] 离开窗口,把 s 减少 x,把哈希表中 x 的出现次数减一。
注意:如果 x 的出现次数变成 0,要从哈希表中删除 x,否则哈希表的大小不正确。
总结:
如果我们想做出来定长滑动窗口的题目有两个必要条件,首先要辨别是否可以运用定长滑窗,其次要知道定长滑窗的解题思路
- 辨别定长滑窗
- 定长滑窗解题套路
1077

被折叠的 条评论
为什么被折叠?



