【力扣】专题训练:定长滑动窗口(更新中)

1.定长子串中元音的最大数目

1456. 定长子串中元音的最大数目 - 力扣(LeetCode)

从这道题目,我们学习了定长滑窗的基本思想

我们要在一串数里面,遍历全部长度为k的子串,来判断元音字母个数

举个例子:长度为8的数组,其中所有长度为3的子串,最多包含元音字母个数

  1. 最简单的想法就是暴力枚举,按索引,0 1 2, 1 2 3,2 3 4……,每次都计算它们的原因字母个数(变量vowel),设立一个标志变量ans,vowel>ans就更新它的值,最后ans就是答案

    但是我们会看到在计算时,0 1 2到1 2 3中,1和2没有改变,我们却重复计算了一遍,怎样优化呢?

  2. 只需要进行 移除0和添加3 的两个操作,012和123都叫窗口(定长为3)

    我们从012到123,就是0出窗口,3进窗口(始终长度为3,窗口前移)

    这就是定长滑窗

依旧以上方例子,窗口长度为3,我们一开始就计算下标0 1的元音个数,因为这样方便我们循环(2进窗口,0出窗口的操作)

示例 1:s=abciiidef, k=3。

  1. 首先统计前 k−1=2 个字母的元音个数,这有 1 个。
  2. s[2]=c 进入窗口,此时找到了第一个长为 k 的子串 abc,现在元音个数有 1 个,更新答案最大值。然后 s[0]=a 离开窗口,现在元音个数有 0 个。
  3. s[3]=i 进入窗口,此时找到了第二个长为 k 的子串 bci,现在元音个数有 1 个,更新答案最大值。然后 s[1]=b 离开窗口,现在元音个数有 1 个。
  4. ……

这就是第一版代码,但是我们可以看到两遍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

滑动时有三步操作:

  1. :下标为 i i i元素进入窗口,统计数量。如果左端点 i − k + 1 < 0 i-k+1<0 ik+1<0,则尚未形成第一个窗口,重复第一步。
  2. 更新:更新答案。一般是更新最大/小值
  3. :下标为 i − k + 1 i-k+1 ik+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

  1. 入:下标为 i i i的元素进入窗口,窗口元素和sum增加。如果 i < 2 k i<2k i<2k,就重复第一步
  2. 更新:记录答案 avgs[i-k]=sum/(2*k+1)
  3. 出:下标为 i − 2 k i-2k i2k的元素离开窗口,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)

这道题的难点在于如何表示数组中互不相同的元素的个数,我们采用哈希表,来统计每个元素出现的次数

  1. 入:元素 x=nums[i] 进入窗口,把 x 加到元素和 s 中,把 x 加到哈希表中(统计 x 的出现次数)。如果 i<k−1 则重复第一步。

  2. 更新:如果哈希表的大小 ≥m,用 s 更新答案的最大值。

  3. 出:元素 x=nums[i−k+1] 离开窗口,把 s 减少 x,把哈希表中 x 的出现次数减一。

    注意:如果 x 的出现次数变成 0,要从哈希表中删除 x,否则哈希表的大小不正确。


总结:

如果我们想做出来定长滑动窗口的题目有两个必要条件,首先要辨别是否可以运用定长滑窗,其次要知道定长滑窗的解题思路

  1. 辨别定长滑窗
  2. 定长滑窗解题套路

  1. 出处:灵茶山艾府https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/solutions/2809359/tao-lu-jiao-ni-jie-jue-ding-chang-hua-ch-fzfo/ ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值