从0到1掌握滑动窗口算法:让LeetCode难题秒变简单
你是否还在为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;
模板关键点解析
- 窗口状态维护:根据问题需求选择合适的数据结构(HashMap/数组/计数器)
- 条件判断:明确窗口需要满足的条件(如包含所有目标字符、无重复元素等)
- 结果更新时机:在扩大或缩小窗口时适时更新最优解
实战案例分析
案例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
高频面试题分类训练
字符串类问题
-
找到字符串中所有字母异位词(LeetCode #438)
- 解题关键:固定窗口大小为模式串长度,滑动窗口比较字符频率
- 难度:中等
- 代码位置:leetcode/string/FindAllAnagramsInAString.java
-
最长重复字符替换(LeetCode #424)
- 解题关键:窗口内最多替换k个字符的最大长度
- 难度:中等
- 代码位置:leetcode/string/LongestRepeatingCharacterReplacement.java
数组类问题
-
长度为K的子数组最大和(LeetCode #53)
- 解题关键:固定窗口大小K,滑动过程中动态计算和
- 难度:简单
- 代码位置:leetcode/array/MaximumSubarraySum.java
-
滑动窗口最大值(LeetCode #239)
- 解题关键:使用双端队列维护窗口内最大值
- 难度:困难
- 代码位置:leetcode/array/SlidingWindowMaximum.java
性能优化技巧
-
使用数组代替HashMap:当处理字符或小范围整数时,使用数组(如int[256])代替HashMap可减少哈希计算开销
// 优化前 Map<Character, Integer> map = new HashMap<>(); // 优化后(处理ASCII字符) int[] count = new int[256]; -
提前终止条件:在寻找最小窗口时,当窗口大小等于目标长度时可直接返回
-
批量移动指针:在某些情况下可计算出左指针需要移动的目标位置,实现跳跃式移动
总结与面试建议
滑动窗口算法是解决数组/字符串子序列问题的利器,掌握其核心思想和通用模板能显著提高解题效率。在面试中,遇到以下特征的问题可优先考虑滑动窗口:
- 问题涉及"子数组"、"子串"、"连续元素"
- 需要求"最大/最小长度"、"包含所有元素"等优化目标
- 暴力解法时间复杂度较高(O(n²)或以上)
建议通过以下步骤准备面试:
- 熟练掌握本文介绍的通用模板
- 完成LeetCode上标签为"滑动窗口"的题目(至少10题)
- 总结不同场景下的窗口状态维护方法
- 练习不看模板独立实现关键题目
滑动窗口算法的灵活性在于窗口状态的维护方式,需要通过大量练习培养对窗口状态的敏感度。记住:算法的本质是对状态的高效管理,而滑动窗口正是这一思想的完美体现。
扩展学习资源
- 官方文档:README.md
- 进阶题目集:company/google/
- 算法复杂度分析:GitHub_Trending_in_interviews_算法复杂度分析.md
祝大家面试顺利,拿下心仪Offer!如果觉得本文有帮助,请点赞收藏,关注作者获取更多算法面试技巧。下一篇我们将深入探讨动态规划在面试中的应用,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



