【算法篇】2.滑动窗口

滑动窗口

209. 长度最小的子数组

  1. 核心思路

滑动窗口的本质是「用两个指针维护一个动态的区间」,通过调整窗口的左右边界,找到满足条件的最小窗口:

  1. 初始化 :左指针left=0,当前窗口和sum=0,最小长度minLen=Integer.MAX_VALUE
  2. 扩展右边界 :右指针right遍历数组,将nums[right]加入窗口和sum
  3. 收缩左边界 :当sum ≥ target时,尝试收缩左边界以找到更小的窗口:
    • 计算当前窗口长度(right - left + 1),更新minLen
    • sum中减去nums[left],左指针left++,直到sum < target
  1. 结果处理 :若minLen未更新(仍为最大值),返回0,否则返回minLen
java 复制代码
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int l=0,r=0,n=nums.length;

        int sum=0,minLen=Integer.MAX_VALUE;

        while(r<n){
            sum+=nums[r];  //积累值
            //大于target值
            while(sum>=target){
                //更新最短
                minLen=Math.min(r-l+1,minLen);
                sum-=nums[l];
                l++;
            }
            r++;
        }
        return minLen==Integer.MAX_VALUE?0:minLen;
    }
}

3. 无重复字符的最长子串

最优解法:滑动窗口 + 数组判重(O (n) 时间 + O (1) 空间)

  1. 核心思路

滑动窗口维护「当前无重复字符的子串区间」,结合数组记录字符最后出现的下标,快速判断字符是否重复并调整窗口左边界:

  1. 初始化
    • 左指针l=0(窗口左边界),记录最长长度maxLen=0
    • 用长度为 128 的数组a(覆盖 ASCII 所有字符),存储每个字符最后出现的下标,初始值为-1(表示未出现)。
  1. 扩展右边界 :右指针r遍历字符串每个字符s.charAt(r)
  2. 判重并调整左边界
    • 若当前字符最后出现的下标a[c] ≥ left(说明字符在当前窗口内重复),则将左指针移至a[c] + 1(跳过重复字符);
  1. 更新状态
    • 记录当前字符的最新下标a[c] = r
    • 计算当前窗口长度r - l + 1,更新maxLen
  1. 返回结果 :遍历结束后返回maxLen
java 复制代码
class Solution {
    public int lengthOfLongestSubstring(String s) {
        //存字符出现的下标
        int[] a=new int[128];
        Arrays.fill(a,-1); //赋值-1
        int n=s.length();
        if(n==0||n==1) return n;
        int l=0,r=0,maxLen=Integer.MIN_VALUE;

        while(r<n){
            int c=(int)s.charAt(r);
            //出现重复的字符:跳到下一个出现的位置(前面的都统计过了,所以不要l=l+1)
            if(a[c]>=l){
                l=a[c]+1;
            }
            a[c]=r;  //更新最新坐标
            maxLen=Math.max(r-l+1,maxLen); ///更新
            r++;
        }
        return maxLen;
    }
}

1004. 最大连续1的个数 III

核心逻辑 :用滑动窗口维护「最多含 k 个 0」的区间,通过调整左右边界找到最大窗口

  1. 核心动作
  • 预处理优化:若数组长度 ≤ k(可翻转所有 0),直接返回数组长度(所有元素可变为连续 1);
  • 滑动窗口遍历
  1. 右指针r遍历数组,统计窗口内 0 的数量cnt
  2. 当窗口内 0 的数量超过 k 时,收缩左指针l,直到cnt ≤ k(保证窗口「最多包含 k 个 0」);
  3. 每次遍历计算当前有效窗口长度,更新最长长度maxLen
  • 返回结果:遍历结束后返回最长有效窗口长度。
  1. 本质

这是滑动窗口解决「最多允许 k 次替换 / 翻转」的连续子数组问题 的核心逻辑:

  • 窗口的「有效性定义」:窗口内 0 的数量 ≤ k(最多翻转 k 个 0 为 1,使窗口内全为连续 1);
  • 滑动窗口的核心是「扩展右边界探索更大窗口,超出限制时收缩左边界维持有效性」,最终找到满足条件的最大窗口长度(即最长连续 1 的长度)。
java 复制代码
class Solution {
    public int longestOnes(int[] nums, int k) {
        int l=0,r=0,n=nums.length;
        if(n<=k) return n;
        int maxLen=Integer.MIN_VALUE;
        int cnt=0; //统计窗口内0的个数
        while(r<n){

            if(nums[r]==0) cnt++;
            //保证窗口内0的个数不超过k个
            //l移动到:窗口第一个0的位置下一个位置
            while(cnt>k){
                if(nums[l]==0){
                    cnt--;
                };
                l++;
            }
            maxLen=Math.max(maxLen,r-l+1);
            r++;
        }
        return maxLen;
    }
}

1658. 将 x 减到 0 的最小操作数

核心转化:直接想"从两端拿数字"很绕

  • 「移除左右元素和为 x」等价于「数组中存在一个连续子数组 ,其和 = 总和 - x」,且该子数组长度最长(因为总长度 - 最长子数组长度 = 最少移除次数);

就是说

  • 总数字和-凑够x的数字和=剩下的数字和
  • 要让「拿的数字最少」就要让「剩下的数字最多」
java 复制代码
class Solution {
    public int minOperations(int[] nums, int x) {
        //左/右减一个转化为 在中间找一个窗口
        //使得  该窗口尽能大->使得两边窗口小
        //和为 sum-x 
        int n=nums.length;
        int l=0,r=0,sum=0;
        for(int i=0;i<n;i++) sum+=nums[i];
        if(sum<x) return -1;
        int cnt=sum-x;  //需要凑的值
        int curSum=0,maxLen=-1;
        while(r<n){
            curSum+=nums[r]; //计算和
            //超过和
            while(cnt<curSum){
                curSum-=nums[l];
                l++;
            }
            //等于才会更新
            if(curSum==cnt) maxLen=Math.max(maxLen,r-l+1);
            r++;
        }
        return maxLen==-1?-1:n-maxLen;
    }
}

904. 水果成篮

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。

  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。

  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

本质:

  • 问题转化 :将「两个篮子装水果 」转化为「找最多含两种元素的最长连续子数组」,是解题核心;
  • 滑动窗口逻辑:扩展右边界探索更大窗口,种类超 2 时收缩左边界维持有效性,全程统计窗口最大长度;
  • 哈希表作用:高效统计窗口内元素种类及数量,保证「种类数 > 2」的判断准确。
java 复制代码
class Solution {
    public int totalFruit(int[] fruits) {
        int n=fruits.length;
        int l=0,r=0;
        // 水果种类,数量
        Map<Integer,Integer> hash=new HashMap<>();
        int maxLen=Integer.MIN_VALUE;
        while(r<n){
            //没有,就设置1
            hash.put(fruits[r],hash.getOrDefault(fruits[r],0)+1);
            //窗口内元素种类超过2个
            while(hash.size()>2){
                int fruit=fruits[l];
                //水果数量减1
                hash.put(fruit,hash.get(fruit)-1);
                //没有水果了
                if(hash.get(fruit)==0) hash.remove(fruit);
                l++;
            }
            maxLen=Math.max(maxLen,r-l+1);
            r++;
        }
        return maxLen;
    }
}

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

核心思路总结

  1. 核心问题转化

将「寻找字符串 s 中 p 的所有异位词起始下标」转化为「寻找 s 中长度等于 p、字符频次与 p 完全匹配的连续子串」,异位词的本质是字符种类和频次完全相同、长度一致。

s的长度都小于的长度,不能是

移动左指针的关键

  • 窗口内出现了p中没有的字符,也就是cnt[cur]<0,必须移动

  • 窗口超过p的长度 ,必须移动左指针

    • 不管有没有符合字母异位词(如果符合,可以加起始索引啦)
  1. 核心实现步骤

(1)预处理与边界防护

  • 用长度为 26 的数组cnt统计字符串 p 中每个小写字母的出现频次(cnt[字符-'a'] 记录对应字符频次);
  • 若 s 的长度小于 p 的长度,直接返回空列表(不可能存在异位词)。

(2)滑动窗口遍历(双指针维护有效窗口)

  • 扩展右边界 :右指针r遍历 s,将当前字符的频次在cnt中减 1;

  • 收缩左边界(频次异常) :若当前字符频次变为负数(说明该字符不在 p 中 / 窗口内该字符频次超过 p 的频次),持续右移左指针l,并恢复左指针对应字符的频次,直到当前字符频次≥0;

  • 校验窗口有效性 :当窗口长度(r-l+1)等于 p 的长度时,判断cnt数组是否全为 0(全 0 表示窗口内字符频次与 p 完全匹配):

    • 若匹配,记录当前左指针l(异位词起始下标);
    • 无论是否匹配,都需右移左指针l并恢复对应字符的频次,继续寻找下一个可能的有效窗口。
  1. 核心判断逻辑
  • isAllEmpty方法判断cnt数组是否全为 0,以此验证窗口内字符频次是否与 p 完全一致;
  • 滑动窗口的核心是「先保证窗口内字符频次不超限(无负数),再校验窗口长度匹配,最终验证频次全匹配」。
java 复制代码
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
         List<Integer> ret=new ArrayList<>();
         int[] cnt=new int[26];
        int l=0,r=0,n=s.length(),pLen=p.length();
        //长度不足,根本找不到
        if(n<pLen) return ret;
        for(int i=0;i<pLen;i++) cnt[p.charAt(i)-'a']++;
        while(r<n){
            int cur=s.charAt(r)-'a';
            cnt[cur]--;
            //移动左指针:出现负数(窗口内出现p没有的字符)
            while(cnt[cur]<0){
                cnt[s.charAt(l)-'a']++;
                l++;
            }
            //移动左指针:符合窗口大小
            if(r-l+1==pLen){
                //窗口内出现字符次数=p的字符出现个数
                if(isAllEmpty(cnt)) ret.add(l);

                //也要移动左指针
                cnt[s.charAt(l)-'a']++;
                l++;
            };
            r++;
        }
        return ret;
    }
    //判断p的字符个数和窗口内字符的个数一样
    public boolean isAllEmpty(int[] cnt){
        for(int i=0;i<26;i++){
            if(cnt[i]!=0) return false;
        }
        return true;
    }
}
相关推荐
汤姆yu2 小时前
基于python大数据的天气可视化及预测系统
大数据·开发语言·python
huohuopro2 小时前
只能录入不能粘贴?这里解决
python
进击的雷神2 小时前
展位号后缀清理、详情页JS数据提取、重试机制控制、地址字段重构——美国NPE展爬虫四大技术难关攻克纪实
javascript·爬虫·python·重构
像素猎人2 小时前
数组中的二分查找函数:lower_bound【第一个 >= 目标值的元素的值或者下标】 和 upper_bound【第一个 > 目标值的元素的值或者下标】
数据结构·算法
yusheng_xyb2 小时前
互联网大厂Java求职面试实录
java·面试·互联网·技术面试
百锦再2 小时前
Spring Boot + JWT + RBAC 权限系统实战,从登录鉴权到接口级权限控制完整落地
java·数据库·spring boot·后端·sql·mysql·oracle
oem1102 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
社畜码农且逊2 小时前
安装虚拟环境工具virtualenv,通过virtualenv myenv创建虚拟环境,激活虚拟环境后安装项目依赖
python·virtualenv
l1t2 小时前
Qwen 3.5plus编写的求解欧拉计划901题python程序优化
开发语言·python