从0到1掌握滑动窗口算法:让LeetCode难题秒变简单

从0到1掌握滑动窗口算法:让LeetCode难题秒变简单

【免费下载链接】interviews Everything you need to know to get the job. 【免费下载链接】interviews 项目地址: https://gitcode.com/GitHub_Trending/in/interviews

你是否还在为LeetCode上的字符串匹配、子数组求和类问题烦恼?是否每次遇到"最小覆盖子串"、"最长无重复子串"都要从头构思解决方案?本文将带你系统掌握滑动窗口(Sliding Window)算法,通过一套通用模板解决80%的数组/字符串子序列问题,让你在面试中轻松应对微软、谷歌等大厂的算法题。

读完本文你将获得:

  • 滑动窗口算法的核心思想与适用场景
  • 一套通用解题模板,3步解决同类问题
  • 5个实战案例分析,覆盖LeetCode高频考点
  • 性能优化技巧,轻松应对大数据量测试用例

滑动窗口算法原理

滑动窗口算法是一种高效处理数组/字符串子序列问题的技巧,它通过维护一个"窗口"(两个指针)在序列上滑动,动态调整窗口大小来寻找满足条件的解。相比暴力枚举的O(n²)时间复杂度,滑动窗口通常能将复杂度降至O(n),是面试中的必备优化手段。

滑动窗口算法示意图

算法核心思想

滑动窗口算法的本质是通过双指针(左指针left和右指针right)界定当前处理的区间,通过移动指针来扩大或缩小窗口范围,同时维护窗口内的状态信息。根据问题特性,滑动窗口可分为:

  • 固定窗口:窗口大小固定,如"长度为k的子数组最大和"
  • 可变窗口:窗口大小动态变化,如"最小覆盖子串"、"最长无重复子串"

通用解题模板

掌握以下三步模板,可解决大多数滑动窗口问题:

// 初始化窗口
int left = 0, right = 0;
// 用于记录窗口状态的数据结构
Map<Character, Integer> window = new HashMap<>();
// 结果变量
int result = 0;

while (right < s.length()) {
    // 1. 移动右指针,扩大窗口
    char c = s.charAt(right);
    right++;
    // 更新窗口状态
    
    // 2. 当窗口满足条件时,移动左指针缩小窗口
    while (window满足条件) {
        // 更新结果
        result = 计算当前结果;
        
        char d = s.charAt(left);
        left++;
        // 更新窗口状态
    }
}

return result;

模板关键点解析

  1. 窗口状态维护:根据问题需求选择合适的数据结构(HashMap/数组/计数器)
  2. 条件判断:明确窗口需要满足的条件(如包含所有目标字符、无重复元素等)
  3. 结果更新时机:在扩大或缩小窗口时适时更新最优解

实战案例分析

案例1:最小覆盖子串(LeetCode #76)

问题描述:给定字符串S和T,在S中找到包含T所有字符的最小子串,时间复杂度要求O(n)。

解题思路:使用可变窗口,通过HashMap记录目标字符出现次数,右指针扩大窗口直到包含所有字符,左指针缩小窗口找到最小长度。

public String minWindow(String s, String t) {
    HashMap<Character, Integer> map = new HashMap<>();
    
    // 初始化目标字符计数
    for(char c : s.toCharArray()) map.put(c, 0);
    for(char c : t.toCharArray()) {
        if(map.containsKey(c)) map.put(c, map.get(c)+1);
        else return ""; // 包含不存在的字符,直接返回空
    }
    
    int start = 0, end = 0, minStart = 0, minLength = Integer.MAX_VALUE;
    int counter = t.length(); // 需要匹配的字符总数
    
    while(end < s.length()) {
        char c1 = s.charAt(end);
        if(map.get(c1) > 0) counter--; // 找到一个需要匹配的字符
        map.put(c1, map.get(c1) - 1);
        end++;
        
        // 当窗口包含所有目标字符时,尝试缩小窗口
        while(counter == 0) {
            // 更新最小窗口
            if(end - start < minLength) {
                minLength = end - start;
                minStart = start;
            }
            
            char c2 = s.charAt(start);
            map.put(c2, map.get(c2) + 1);
            if(map.get(c2) > 0) counter++; // 窗口不再满足条件
            start++;
        }
    }
    
    return minLength == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLength);
}

完整代码实现:leetcode/string/MinimumWindowSubstring.java

案例2:无重复字符的最长子串(LeetCode #3)

问题描述:给定一个字符串,找出不含有重复字符的最长子串的长度。

解题思路:使用滑动窗口记录当前无重复子串,当遇到重复字符时,将左指针移动到重复字符的下一位。

public int lengthOfLongestSubstring(String s) {
    if (s.length() == 0) return 0;
    
    HashMap<Character, Integer> map = new HashMap<>();
    int max = 0;
    int left = 0;
    
    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        // 如果字符已存在且位置在当前窗口内,则移动左指针
        if (map.containsKey(c) && map.get(c) >= left) {
            left = map.get(c) + 1;
        }
        map.put(c, right); // 更新字符位置
        max = Math.max(max, right - left + 1); // 更新最大长度
    }
    
    return max;
}

完整代码实现:leetcode/string/LongestSubstringWithoutRepeatingCharacters.java

高频面试题分类训练

字符串类问题

  1. 找到字符串中所有字母异位词(LeetCode #438)

    • 解题关键:固定窗口大小为模式串长度,滑动窗口比较字符频率
    • 难度:中等
    • 代码位置:leetcode/string/FindAllAnagramsInAString.java
  2. 最长重复字符替换(LeetCode #424)

    • 解题关键:窗口内最多替换k个字符的最大长度
    • 难度:中等
    • 代码位置:leetcode/string/LongestRepeatingCharacterReplacement.java

数组类问题

  1. 长度为K的子数组最大和(LeetCode #53)

    • 解题关键:固定窗口大小K,滑动过程中动态计算和
    • 难度:简单
    • 代码位置:leetcode/array/MaximumSubarraySum.java
  2. 滑动窗口最大值(LeetCode #239)

    • 解题关键:使用双端队列维护窗口内最大值
    • 难度:困难
    • 代码位置:leetcode/array/SlidingWindowMaximum.java

性能优化技巧

  1. 使用数组代替HashMap:当处理字符或小范围整数时,使用数组(如int[256])代替HashMap可减少哈希计算开销

    // 优化前
    Map<Character, Integer> map = new HashMap<>();
    
    // 优化后(处理ASCII字符)
    int[] count = new int[256];
    
  2. 提前终止条件:在寻找最小窗口时,当窗口大小等于目标长度时可直接返回

  3. 批量移动指针:在某些情况下可计算出左指针需要移动的目标位置,实现跳跃式移动

总结与面试建议

滑动窗口算法是解决数组/字符串子序列问题的利器,掌握其核心思想和通用模板能显著提高解题效率。在面试中,遇到以下特征的问题可优先考虑滑动窗口:

  • 问题涉及"子数组"、"子串"、"连续元素"
  • 需要求"最大/最小长度"、"包含所有元素"等优化目标
  • 暴力解法时间复杂度较高(O(n²)或以上)

建议通过以下步骤准备面试:

  1. 熟练掌握本文介绍的通用模板
  2. 完成LeetCode上标签为"滑动窗口"的题目(至少10题)
  3. 总结不同场景下的窗口状态维护方法
  4. 练习不看模板独立实现关键题目

滑动窗口算法的灵活性在于窗口状态的维护方式,需要通过大量练习培养对窗口状态的敏感度。记住:算法的本质是对状态的高效管理,而滑动窗口正是这一思想的完美体现。

扩展学习资源

  • 官方文档README.md
  • 进阶题目集company/google/
  • 算法复杂度分析:GitHub_Trending_in_interviews_算法复杂度分析.md

祝大家面试顺利,拿下心仪Offer!如果觉得本文有帮助,请点赞收藏,关注作者获取更多算法面试技巧。下一篇我们将深入探讨动态规划在面试中的应用,敬请期待!

【免费下载链接】interviews Everything you need to know to get the job. 【免费下载链接】interviews 项目地址: https://gitcode.com/GitHub_Trending/in/interviews

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值