滑动窗口和双指针题单

目录

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

思路分析:

代码实现:

题目2:643. 子数组最大平均数 I

思路分析:

代码实现:

题目3:1343. 大小为 K 且平均值大于等于阈值的子数组数目

思路分析:

代码实现:

题目4:2090. 半径为 k 的子数组平均值

思路分析:

代码实现:

题目5:2379. 得到 K 个黑块的最少涂色次数

思路分析:

代码实现:

题目6:1052. 爱生气的书店老板

思路分析:

代码实现:

题目7:1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

思路分析:

代码实现:

题目8:2841. 几乎唯一子数组的最大和

思路分析:

代码实现:

题目9:2461. 长度为 K 子数组中的最大和

思路分析:

代码实现:

题目10:1423. 可获得的最大点数

思路分析:

代码实现:

题目11:1652. 拆炸弹 

思路分析:

代码实现:

题目12:1297. 子串的最大出现次数

思路分析:

代码实现:


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

思路分析:

这个题目就是窗口大小固定为k的滑动窗口,在遍历数组的时候,始终保持窗口的元素有k个,判断这k个元素种元音的数量,当窗口往后滑动的时候,需要将窗口最前面的元素去除,然后再添加一个新的元素。

代码实现:

public int maxVowels(String s, int k) {
        //将字符串转换为字符数组
        char ch[]=s.toCharArray();
        int cnt=0,max=0;
        for(int i=0;i<ch.length;i++){
            if(ch[i]=='a'||ch[i]=='e'||ch[i]=='i'||ch[i]=='o'||ch[i]=='u'){
                cnt++;
            }
            //窗口还未达到k的时候,后续的逻辑操作不执行
            if(i<k-1){
                continue;
            }
            max=Math.max(max,cnt);
            //窗口最前面的元素,被踢出去,更新窗口内元音字母的个数
            char tmp=ch[i-k];
            if(tmp=='a'||tmp=='e'||tmp=='i'||tmp=='o'||tmp=='u'){
                cnt--;
            }
        }
        return max;
    }

题目2:643. 子数组最大平均数 I

思路分析:

这个题目同样是始终维持一个定长的窗口,窗口大小为k,在遍历数组的时候,窗口的前面不断删除元素,尾部不断添加元素,但是窗口的大小一直是k。找到最大平均值就行。

代码实现:

public double findMaxAverage(int[] nums, int k) {
        double sum=0;
        double max=Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            //如果窗口内的元素还未到k个,就不做后续的逻辑处理
            if(i<k-1){
                continue;
            }
            double avg=sum/k;
            //找到最大的平均值
            max=Math.max(max,avg);
            sum-=nums[i-k+1];
        }
        return max;
    }

题目3:1343. 大小为 K 且平均值大于等于阈值的子数组数目

思路分析:

同样是一样的方式,维持一个固定大小的滑动窗口就行

代码实现:

public int numOfSubarrays(int[] arr, int k, int threshold) {
        //定义总和
        int sum=0;
        //统计大于threshold的数目
        int cnt=0;
        for(int i=0;i<arr.length;i++){
            sum+=arr[i];
            if(i<k-1){
                continue;
            }
            int avg=sum/k;
            if(avg>=threshold) cnt++;
            //删除窗口最前面的元素,方便后续添加新的元素。
            sum-=arr[i-k+1];
        }
        return cnt;
    }

题目4:2090. 半径为 k 的子数组平均值

思路分析:

这个题目需要思维稍微转变一点,求半径为k的子数组,那么也就是说这个窗口是2*k+1这么大,我们只需要维护这么一个大小的窗口就行,满足条件的就求出平均值,不满足条件的统统都是-1。

代码实现:

class Solution {
    public int[] getAverages(int[] nums, int k) {
        //定义一个存储平均值的数组
        int avgArr[]=new int[nums.length];
        //先对所有值都赋值-1,如果有满足后续条件的才更新具体的值,否则不满足条件的都是-1
        Arrays.fill(avgArr,-1);
        long sum=0L;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            //维护一个窗口2*k+1 这也就是说i前面k个元素和i后面k个元素都存在的情况下
            if(i<2*k){
                continue;
            }
            //求出当前窗口内的平均值
            int avg=(int)(sum/(2*k+1));
            avgArr[i-k]=avg;
            sum-=nums[i-2*k];
        }
        return avgArr;
    }
}

题目5:2379. 得到 K 个黑块的最少涂色次数

思路分析:

这个题目只需要求出k范围内,白色块最少的数目就行。因为题目中就是求最少涂成黑色块的次数,那么只需要找到固定窗口内最少白色块数目就行。

代码实现:

class Solution {
    public int minimumRecolors(String blocks, int k) {
        char ch[]=blocks.toCharArray();
        int cnt=0;
        int min=Integer.MAX_VALUE;
        for(int i=0;i<ch.length;i++){
            if(ch[i]=='W') cnt++;
            if(i<k-1) continue;
            min=Math.min(min,cnt);
            if(ch[i-k+1]=='W') cnt--;
        }
        return min;
    }
}

题目6:1052. 爱生气的书店老板

思路分析:

这个题目稍微难一点,因为这个题目不仅仅需要考虑minutes局部内的最优解,还需要考虑全局,因为题目中说的时一天营业下来,最多有多少客户满意。这题目需要分两步求解。

首先,求出一天内,满意顾客的总数目(也就是老板没有生气的时候)。

其次,就是求出minutes内,顾客不满意的最大数目,我们只需要让这一部分顾客满意了,那么就是局部最优解。

以上两者加起来就是最终一天内,顾客满意的最大数目。

代码实现:

class Solution {
    public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
        //1.先计算出那些老板未生气时候,满意用户的总数目
        int sum=0;
        for(int i=0;i<customers.length;i++){
            if(grumpy[i]==0) sum+=customers[i];
        }
        //2.算出在连续的minutes分钟内,老板生气时,不满意用户的最大数目max
        //只需要让这一批不满意用户,变为满意,加上sum就行
        int cnt=0;
        int max=Integer.MIN_VALUE;
        for(int i=0;i<customers.length;i++){
            //累加minutes分钟内,不满意用户的数量
            if(grumpy[i]==1) cnt+=customers[i];
            if(i<minutes-1) continue;
            max=Math.max(max,cnt);
            if(grumpy[i-minutes+1]==1) cnt-=customers[i-minutes+1];
        }
        return max+sum;
    }
}

题目7:1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

思路分析:

这个题目的意思就是,在字符串s中,找到那些长度为k的子串。这些子串是否恰好满足于长度为k的字符串的所有情况。例如k=2,可能的存在的二进制字符串时00,01,10,11。然后我们只需要看看字符串s中,是否以上四种情况的子串都出现了,如果都出现了,则返回true。这个题目很明显可以借助hash表的去重特性,将字符串中所有长度为k的子串全部存在哈希表中,去重以后,判断hash表中元素数目是否等于2^k就行。

代码实现:

public boolean hasAllCodes(String s, int k) {
        //k=2 可以有二进制子串00 01 10 11
        //所以我们可以借助hash表去重的特性,遍历一遍字符串s,每次都取长度为k的子串
        //将这些子串添加进hash表中,最终判断一下hash表中的元素数目是否等于2^k即可
        HashSet<String> hashSet=new HashSet<>();
        int left=0;
        int cnt=(int)Math.pow(2,k);
        for(int i=0;i<s.length();i++){
            if(i<k-1) continue;
            hashSet.add(s.substring(left,i+1));
            left++;
        }
        return hashSet.size()==cnt;
    }

题目8:2841. 几乎唯一子数组的最大和

思路分析:

这个题目其实很好理解,但是代码不好实现,肯定还是滑动窗口的思想,但是怎么实现窗口内独立元素的记数呢,需要借助hashmap,用hashmap统计元素出现的次数。如果当前元素在hashmap中没有出现,说明它是唯一元素。在窗口往前滑动的时候,将移除的那个元素在hashmap中更新一下。

  1. 滑动窗口

    • 维护一个固定大小为 k 的窗口,窗口在数组上滑动。

    • 每次滑动时,窗口会移除一个旧元素(窗口最左边的元素),并加入一个新元素(窗口最右边的元素)。

  2. 哈希表的作用

    • 使用哈希表记录窗口内每个元素的出现次数。

    • 通过哈希表可以快速判断一个元素是否是窗口内的唯一元素。

    • 当元素被移除时,更新哈希表中该元素的计数。如果某个元素的计数降为 0,说明该元素在窗口中完全消失,需要减少独立元素的计数。

  3. 独立元素的计数

    • 使用一个变量 uniqueCnt 来记录当前窗口内独立元素的个数。

    • 当新元素加入窗口时,如果该元素在哈希表中的计数为 0,说明它是新的独立元素,uniqueCnt 加 1。

    • 当旧元素被移除时,如果该元素在哈希表中的计数降为 0,说明它不再是窗口内的独立元素,uniqueCnt 减 1。

代码实现:

class Solution {
    public long maxSum(List<Integer> nums, int m, int k) {
        //HashMap用来统计元素出现的次数
        HashMap<Integer,Integer> hashMap=new HashMap<>();
        //记录每个窗口内不相同元素的个数
        int uniqueCnt=0;
        //记录窗口内的和
        long sum=0;
        long max=0;
        for(int i=0;i<nums.size();i++){
            //对于每个元素,先把它累加起来
            sum+=nums.get(i);
            /*
            再将元素加入hashmap中,如果hashmap中不存在该元素,就赋值为1,
            表示这个元素在窗口内第一次出现
            */
            hashMap.put(nums.get(i),hashMap.getOrDefault(nums.get(i),0)+1);
            //如果元素第一次出现,就记录一下这种唯一元素的个数
            if(hashMap.get(nums.get(i))==1) uniqueCnt++;
            if(i<k-1) continue;
            //当窗口内的唯一元素达到m,说明这就是一个满足条件的唯一子数组
            if(uniqueCnt>=m) max=Math.max(max,sum);
            //窗口开始挪动
            sum-=nums.get(i-k+1);
            //并且在hashmap中也更新一下
            hashMap.put(nums.get(i-k+1),hashMap.get(nums.get(i-k+1))-1);
            //如果元素在hashmap中不存在了,更新一下uniqueCnt的数目
            if(hashMap.get(nums.get(i-k+1))==0) uniqueCnt--;
        }
        return max;
    }
}

题目9:2461. 长度为 K 子数组中的最大和

思路分析:

思路和实现方式同上一题,直接秒杀

代码实现:

class Solution {
    public long maximumSubarraySum(int[] nums, int k) {
        HashMap<Integer,Integer> hashMap=new HashMap<>();
        long sum=0,max=0,unique=0;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            hashMap.put(nums[i],hashMap.getOrDefault(nums[i],0)+1);
            if(hashMap.get(nums[i])==1) unique++;
            if(i<k-1) continue;
            if(unique==k) max=Math.max(max,sum);
            sum-=nums[i-k+1];
            hashMap.put(nums[i-k+1],hashMap.get(nums[i-k+1])-1);
            if(hashMap.get(nums[i-k+1])==0) unique--;
        }
        return max;
    }
}

题目10:1423. 可获得的最大点数

思路分析:

这个题目利用反向思维,既然要取牌,而且每次都是从最左边或者最右边取牌,就保证了剩下这些牌的连续性,那么剩下的这些牌,我们是不是可以看作是一个窗口,那么我们只需要找到最小窗口和,就说明取出去的那些牌的点数一定是最大的。

代码实现:

class Solution {
    public int maxScore(int[] cardPoints, int k) {
        //需要取k张牌,那也就是说会留下cardPoints.length-k张牌
        //所以如果留下来的这一部分牌的和是最小值,那就说明取出去的k张牌的和是最大值
        int size=cardPoints.length-k;
        int sum=0;
        for(int num:cardPoints) sum+=num;
        //如果k张牌都取,直接返回结果就行
        if(size==0) return sum;
        int min=Integer.MAX_VALUE,res=0;
        for(int i=0;i<cardPoints.length;i++){
            res+=cardPoints[i];
            if(i<size-1) continue;
            //找到最小窗口数组和
            min=Math.min(res,min);
            res-=cardPoints[i-size+1];
        }
        //用总和减去剩下牌的最小和,就求出取出牌的最大和
        return sum-min;
    }
}

题目11:1652. 拆炸弹 

思路分析:

这个题目的难度在于循环数组如何解决,其余的并不难。

代码实现:

public int[] decrypt(int[] code, int k) {
        int arr[]=new int[code.length];
        if(k==0) return arr;
        for(int i=0;i<code.length;i++){
            int sum=0;
            if(k>0){
                for(int j=1;j<=k;j++){
                    sum+=code[(i+j)%code.length];
                }
            }else{
                for(int j=1;j<=-k;j++){
                    sum+=code[(i-j+code.length)%code.length];
                }
            }
            arr[i]=sum;
        }
        return arr;
    }

题目12:1297. 子串的最大出现次数

思路分析:

首先,这个题目感觉难点在于窗口不固定,但是如果仔细思考一下问题,就会发现,如果一个长字符串重复出现,那么这个长字符串的子串也是重复出现的。例如,abc,abc,ab。这一组数据中,abc出现了两次,那么ab也相当于重复了两次,并且最后一个字符串也是ab,ab就出现了三次,所以如果选择短字符串,可能得到更大的子串出现次数。那么我们就可以确定窗口的大小是minSize了。

代码实现:

class Solution {
    public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
        int left=0;
        int max=0;
        //需要借助hashmap,用来记录字符串出现的次数
        HashMap<String,Integer> map=new HashMap<>();
        for(int i=0;i<s.length();i++){
            if(i<minSize-1) continue;
            //找到长度为minSize的子串
            String tmp=s.substring(left,i+1);
            //并且子串中不同字母个数是小于maxLetters才行,记录满足条件的子串的出现次数
            if(count(tmp)<=maxLetters){
                map.put(tmp,map.getOrDefault(tmp,0)+1);
                //找到子串出现的最大次数
                max=Math.max(max,map.get(tmp));
            }
            //窗口开始移动
            left++;
        }
        return max;
    }
    //统计子串中,不同字母的出现次数
    public int count(String string){
        char ch[]=string.toCharArray();
        int cnt[]=new int[26];
        int unique=0;
        for(int i=0;i<ch.length;i++){
            cnt[ch[i]-'a']++;
            //说明该字母是第一次出现
            if(cnt[ch[i]-'a']==1) unique++;
        }
        return unique;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值