【滑动窗口】滑窗模板,在小小的算法题里滑呀滑呀滑

news/2024/5/20 8:06:18 标签: 算法, 滑动窗口

首先大家先喊出我们的口号:跟着模板搞,滑窗没烦恼!

 

一.什么是滑动窗口

滑动窗口算法是双指针算法的一种特定化的算法模型,常用于在特定的条件下求最大或者最小的字符串,特定的数组,以及字符序列等相关问题,使用滑动窗口的目的也很简单:使原本需要使用双循环嵌套来解决的问题通过双指针来解决,从而使时间复杂度大大降低(一般而言是将O(n^2)的时间复杂度降至O(n))

二.滑动窗口的框架和模板

滑动窗口题目的痛点

很多小伙伴在刷LeetCode题目时可能会有这样的感觉:好像这个题是使用滑动窗口来进行解决,怎么解决呢?接着列出了left和right两个指针,之后就不知所措了,这主要对整个滑动窗口算法的执行流程很陌生而导致的,除此之外,即使我们看了题解,知道了解决这个题的整体思路,当我们过两天再来看这个题目的时候,由于缺失了对滑动窗口算法的某些特定步骤的启发,我们还是对这个题目不知所措,经过大佬们们不断的刷题和对滑动窗口算法的总结,滑动窗口算法模型应运而生~

使用滑动窗口框架的优势

滑动窗口框架的优势我们可以从以下几点进行说明:1.使用滑动窗口的框架,能降低整道题目的难度,在特定的难点能够给我们带来启发2.使用滑动窗口的框架,能够使我们将更多的精力放到核心思想的思考上,提高我们刷题的效率。3.使用模板进行开发,当我们回头再来看这个题目时,记忆也会直接锁定到题目中的难点,记忆会更加深刻。

滑动窗口的框架模板如下

最长模板

模板的实现思路如下:我们在使用滑动窗口的模板时,对求最长和最短的字符序列或者字符串给出两种不同的实现思路以及模板代码:
求最长相关的实现思路:

左右指针(LR)在起始位置,R指针逐步向右循环滑动,在每次滑动的过程中,如果滑动窗口内的元素满足滑动窗口的需求,R指针不断向右滑动扩大窗口,并不断更新最优结果;如果窗内元素不满足条件,L指针不断向右缩小窗口,在此过程中并不断更新结果,直到R到达结尾

初始化 left,right,result,bestResult
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前result
    while("result不满足要求"){
        窗口缩小,移除left对应元素,left右移
    }
    更新最优结果bestResult
    right++;
}
返回bestResult

最短模板

实现思路:

左右指针(LR)初始在起始点,R逐步向右滑动,在每次滑动的过程中,如果窗口内的元素满足条件,L向右缩小窗口,并不断更新最小的结果,如果窗口内的元素不满足条件,R不断向右扩大窗口,直到R到达字符串的结尾

初始化 left,right,result,bestResult
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前result
    while("result满足要求"){
        更新最优结果bestResult
        窗口缩小,移除left对应元素,left右移
    }
    right++;
}
返回bestResult

三.实战分析

3.1 和大于等于 target 的最短子数组

题目描述

解题思路

按照我们上面给出模板的逻辑,我们给出以下的解题思路:定义两个指针(LR),由于这个题目求的是最短的子数组,套用上面的模板,当不满足条件的时候R不断向右移动,当满足条件之后L指针向右移动不断缩小窗口中元素的数量,最终求得所求数组数量的最小值

示例代码

我们给出实例代码:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        //设置最小个数
        int count=0;
        int left=0;
        int right=0;
        int sum=0;
        int minLength=Integer.MAX_VALUE;
        //进行遍历
        while(right<nums.length){
            sum+=nums[right];
            count++;
            while(sum>=target){
                if(count<minLength){
                    minLength=count;
                }
                sum-=nums[left];
                 left++;
                count--;
            }
            right++;
        }
        if(minLength==Integer.MAX_VALUE){
            return 0;
        }
        return minLength;
    }
}

3.2 最小覆盖子串

题目描述

解题思路

大家不要被这道试题的难度吓到,我们要始终相信:跟着模板搞,滑窗没烦恼!ok,接下来讲述我们这道题的解题思路:这道题是求覆盖目标子串所需要的子串数量,我们首先使用一个hashmap对目标子串所需要的字符以及每个字符所需要的数量进行遍历,定义LR两个指针,由于这个题目仍然是求最小子串,当我们遍历的字符串不满足要求时R指针不断向右遍历,并对窗口内的元素使用另一个hashmap进行存储,当我们遍历过的指针能够满足目标串所需相关字符的数量,这时我们再使L指针不断向右缩减窗口内元素的数量,不断循环这个过程,直到R走到字符串的最后一个元素

示例代码

我们给出示例代码:

class Solution {
    public String minWindow(String s, String t) {
        //创建双指针
        int left=0;
        int right=0;
        //定义两个hashmap
        HashMap<Character,Integer>map1=new HashMap<>();
        HashMap<Character,Integer>map2=new HashMap<>();
        int start=0;
        int len=Integer.MAX_VALUE;
        //设置要求
        int need=0;
        //设置满足情况
        int satisfy=0;
        //遍历要求
        for(char c: t.toCharArray()){
            map1.put(c,map1.getOrDefault(c,0)+1);
            if(map1.get(c)==1){
                need++;
            }
        }
        //进行遍历
        int n=s.length();
        while(right<n){
            map2.put(s.charAt(right),map2.getOrDefault(s.charAt(right),0)+1);
            if(map2.get(s.charAt(right)).equals(map1.get(s.charAt(right)))){
                satisfy++;
            }
            //优化结果
            while(satisfy==need){
                if(len>right-left+1){
                    len=right-left+1;
                    start=left;
                }
                char rm=s.charAt(left);
                if(map1.containsKey(rm)){
                    if(map1.get(rm).equals(map2.get(rm)))
                    satisfy--;
                }
                map2.put(rm,map2.get(rm)-1);
                left++;
            }
            right++;
        }
        if(len==Integer.MAX_VALUE){
            return "";
        }
        return s.substring(start,start+len);
    }
}

3.3 字符串的排列

题目描述

 

解题思路

这道题与上面的那道题目类似,同样是需要两个hashmap,一个是用来存储标准的字符以及其每个字符对应的元素的个数,按照上题的思路同理进行遍历,最后有一点不同的是我们最后将得到的最小值进行与符合要求的长度进行比对,相等返回TRUE ,不相等返回FALSE

示例代码

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        //创建双指针
        int left=0;
        int right=0;
        //创建hashmap
        int[]nums1=new int[26];
        int[]nums2=new int[26];
        int maxLength=Integer.MAX_VALUE;
        int ask=0;
        int satisfy=0;
        //遍历要求
        for(int i=0;i<s1.length();++i){
            nums1[s1.charAt(i)-'a']++;
            if(nums1[s1.charAt(i)-'a']==1){
                ask++;
            }
        }
        //进行遍历
        while(right<s2.length()){
            nums2[s2.charAt(right)-'a']++;
            if(nums2[s2.charAt(right)-'a']==nums1[s2.charAt(right)-'a']){
                satisfy++;
            }
            while(satisfy==ask){
                if(right-left+1<maxLength){
                    maxLength=right-left+1;
                }
                if(nums2[s2.charAt(left)-'a']==(nums1[s2.charAt(left)-'a'])){
                    satisfy--;
                }
                nums2[s2.charAt(left)-'a']--;
                left++;
            }
            right++;
        }
        return maxLength==s1.length()?true:false;
    }
}

3.4 找到字符串中所有字母异位词

题目描述

 

解题思路

同样是与上题类似的解法,唯一的区别是在上题的解题基础上我们对每次遍历的minLength进行统计,如果符合我们条件的需求我们就直接将这个集合加入list即可

示例代码

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //创建list
        List<Integer>list=new ArrayList<>();
        //基本类似的解题思路:
        //创建双指针
        int left=0;
        int right=0;
        //创建两个哈希表
         int[]nums1=new int[26];
        int[]nums2=new int[26];
        int ask=0;
        int satisfy=0;
        //遍历要求
        for(int i=0;i<p.length();++i){
            nums1[p.charAt(i)-'a']++;
            if(nums1[p.charAt(i)-'a']==1){
                ask++;
            }
        }
        //遍历实际的字符串
        while(right<s.length()){
            nums2[s.charAt(right)-'a']++;
            if(nums1[s.charAt(right)-'a']==nums2[s.charAt(right)-'a']){
                satisfy++;
            }
            //优化结果
            while(satisfy==ask){
                if(right-left+1==p.length()){
                    list.add(left);
                }
                if(nums2[s.charAt(left)-'a']==(nums1[s.charAt(left)-'a'])){
                    satisfy--;
                }
                nums2[s.charAt(left)-'a']--;
                left++;
            }
            right++;
        }
        return list;
    }
}

3.5 无重复字符的最长子串

题目描述

解题思路

这个题与前面的题目就有相对较大的区别了,从滑动窗口的种类来看,这个求的是最大的滑动窗口的大小,我们具体描述一下这道题的解题流程:创建hash表来存储我们每次进行遍历的元素,创建双指针进行遍历,R指针不断进行向右遍历每遍历一个元素我们就将元素加入到hash表中,当窗口中的元素数量不满足题目要求时(某个元素出现了两次),这时我们就将L指针不断向右移,直到满足题目中的要求,最后将我们最大的maxLength进行返回即可。

示例代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        
        //创建哈希表
        HashMap<Character,Integer>map=new HashMap<>();
        //定义双指针
        int left=0;
        int right=0;
        //定义总长度
        int n=s.length();
        int maxLength=0;
        while(right<n){
            map.put(s.charAt(right),map.getOrDefault(s.charAt(right),0)+1);
            while(map.get(s.charAt(right))>1){
                 map.put(s.charAt(left),map.get(s.charAt(left))-1);
                 left++;
            }
            if(right-left+1>maxLength){
                maxLength=right-left+1;
            }
            right++;
        }
       return maxLength; 
    }
}

3.6 考试的最大困扰度

题目描述

 

解题思路

这个题也是求最小的窗口,我们的解题思路也相对明确:就是用给定的k个万能值来填补两部分TRUE或者FALSE之间的空缺,R指针不断向右移,如果k无法满足空缺的话,L向右移动,直到K能够满足,一直遍历直到R指针遍历到字符串的最后

示例代码

class Solution {
    public int maxConsecutiveAnswers(String answerKey, int k) {
        //核心思路:使用提供的k的个万能值,来填补TRUE和FALSE之间的空缺值
        //创建两个指针
        int left=0;
        int right=0;
        //创建计数器
        int countT=0;
        int countF=0;
        int n=answerKey.length();
        int maxLength=0;
        //进行遍历循环
        while(right<n){
            char c=answerKey.charAt(right);
            
            if(c=='T'){
                countT++;
            }else{
                countF++;
            }
            //不符合的情况
            while(countT>k&&countF>k){
                char d=answerKey.charAt(left);
                //进行遍历
                if(d=='T'){
                    countT--;
                }else{
                    countF--;
                }
                left++;
            }
            if(right-left+1>maxLength){
                maxLength=right-left+1;
            }
            right++;
        }
        return maxLength;

    }
}


http://www.niftyadmin.cn/n/333770.html

相关文章

Mysql索引底层原理及其优化方案

1.深入理解Mysql索引底层数据结构与算法 1.1索引结构 索引及其数据结构&#xff1a; 二叉树红黑树Hash表B-Tree 1.1 二叉树 说明&#xff1a;二叉树是建立数据后&#xff0c;会和第一元素进行比对&#xff0c;当比较的元素小于第一个元素时&#xff0c;此时就会走第一个元素…

PDF大文件批量去除水印,又一个省心小妙招

PDF大文件批量去除水印&#xff0c;又一个省心小妙招 适用场景&#xff1a;本教程适合批量去除文件量较大的PDF文档内的图片水印。 使用软件&#xff1a;Python&#xff1b; 需安装第三方库&#xff1a;PIL&#xff0c;fitz&#xff0c;pymupdf pip install PIL pip install…

Java中的线程是什么?如何实现线程

在Java中&#xff0c;线程是一种执行路径&#xff0c;用于执行程序中的代码。线程的实现可以提高程序的性能和并发性。在本文中&#xff0c;我们将探讨Java中的线程是什么&#xff0c;以及如何实现线程。 什么是线程&#xff1f; 线程是程序执行的一条路径。在Java中&#xf…

python path解析基础

XPath是一种用于在XML和HTML文档中选择元素的语言。在Python中&#xff0c;我们可以使用lxml库来解析XML和HTML文件&#xff0c;并使用XPath表达式来选择和提取数据。在本文中&#xff0c;我们将深入研究如何使用Python和lxml解析XML和HTML文件&#xff0c;并使用XPath表达式从…

Java-API简析_java.lang.Integer类(基于JDK1.8)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/130730986 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

Shell脚本函数应用

记录&#xff1a;429 场景&#xff1a;Shell脚本函数应用&#xff0c;定义函数&#xff0c;函数调用&#xff0c;函数传参。 版本&#xff1a;CentOS Linux release 7.9.2009。 1.普通函数 示例普通函数是没有入参和返回值。 1.1脚本 脚本名称&#xff1a;b2023051701.sh…

快速入门matlab——数值字符逻辑显示和函数句柄

matlab初始化数组 matlab数组循环赋值 matlab中计算数组的长度 cell元胞数组 学习目标&#xff1a; 1 学会数据显示格式的设定 2 逻辑真和假 3 数值转换成逻辑值 4 字符和字符串的关系 …

【C++】初始化列表 | 关键字 explicit | static成员

目录 1. 初始化列表 使用时注意事项&#xff1a; 2. 关键字 explicit 3. static成员 3.1 概念 2.2 特性 1. 初始化列表 构造函数体中的语句只能将其称为赋初值&#xff0c;而不能称作初始化。因为初始化只能初始化一次&#xff0c;而构造函数体内可以多次赋值。 初始化列…