别再暴力枚举了!用Python和Java搞定LeetCode滑动窗口高频题(附避坑指南)

张开发
2026/4/20 15:02:42 15 分钟阅读

分享文章

别再暴力枚举了!用Python和Java搞定LeetCode滑动窗口高频题(附避坑指南)
滑动窗口算法实战Python与Java双语言解LeetCode高频题滑动窗口算法是解决数组和字符串子序列问题的利器尤其适合处理连续子数组/子串类题目。很多开发者在面试中遇到这类问题时要么陷入暴力枚举的泥潭要么虽然知道滑动窗口的概念却在实现时频频踩坑。本文将用Python和Java两种语言带你拆解LeetCode上最常考的7类滑动窗口问题同时揭示那些面试官不会明说但一定会考察的实现细节。1. 滑动窗口算法核心思想滑动窗口本质上是一种双指针技术的变体它通过维护一个动态变化的窗口来避免重复计算。想象你正在观察一个不断滑动的窗户右边界不断扩展以探索新元素左边界则在满足特定条件时收缩以优化解。算法框架的三要素窗口定义通常用left和right指针表示当前窗口的左右边界扩展条件何时移动right指针扩大窗口寻找可行解收缩条件何时移动left指针缩小窗口优化可行解# Python基础模板 def sliding_window(s): left right 0 # 初始化窗口边界 window {} # 用于记录窗口内元素状态 while right len(s): # 扩展窗口 window[s[right]] window.get(s[right], 0) 1 right 1 # 收缩窗口的条件 while window_needs_shrink(window): # 更新窗口状态 window[s[left]] - 1 if window[s[left]] 0: del window[s[left]] left 1// Java基础模板 public int slidingWindow(String s) { int left 0, right 0; // 窗口边界 MapCharacter, Integer window new HashMap(); while (right s.length()) { // 扩展窗口 char c s.charAt(right); window.put(c, window.getOrDefault(c, 0) 1); right; // 收缩窗口的条件 while (windowNeedsShrink(window)) { // 更新窗口状态 char d s.charAt(left); window.put(d, window.get(d) - 1); if (window.get(d) 0) { window.remove(d); } left; } } return ...; }2. 最小覆盖子串问题LeetCode 76这是滑动窗口的经典问题给定字符串S和T在S中找到包含T所有字符的最短子串。Python实现要点使用collections.defaultdict简化计数用formed变量跟踪已匹配的字符种类数收缩条件为当前窗口已包含T的所有字符from collections import defaultdict def minWindow(s: str, t: str) - str: need defaultdict(int) for c in t: need[c] 1 left formed 0 min_len float(inf) result window defaultdict(int) for right, c in enumerate(s): if c in need: window[c] 1 if window[c] need[c]: formed 1 while formed len(need): if right - left 1 min_len: min_len right - left 1 result s[left:right1] left_char s[left] if left_char in need: window[left_char] - 1 if window[left_char] need[left_char]: formed - 1 left 1 return resultJava实现差异点使用HashMap替代Python的defaultdict字符处理需显式类型转换字符串切片操作效率较低应记录索引而非子串public String minWindow(String s, String t) { MapCharacter, Integer need new HashMap(); for (char c : t.toCharArray()) { need.put(c, need.getOrDefault(c, 0) 1); } int left 0, formed 0; int minLen Integer.MAX_VALUE; int start 0; MapCharacter, Integer window new HashMap(); for (int right 0; right s.length(); right) { char c s.charAt(right); if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) 1); if (window.get(c).equals(need.get(c))) { formed; } } while (formed need.size()) { if (right - left 1 minLen) { minLen right - left 1; start left; } char leftChar s.charAt(left); if (need.containsKey(leftChar)) { window.put(leftChar, window.get(leftChar) - 1); if (window.get(leftChar) need.get(leftChar)) { formed--; } } left; } } return minLen Integer.MAX_VALUE ? : s.substring(start, start minLen); }3. 无重复字符的最长子串LeetCode 3寻找不包含重复字符的最长子串长度这是滑动窗口的典型应用场景。关键实现技巧使用哈希集合记录当前窗口字符当遇到重复字符时从左边界开始逐个移除字符直到重复字符被移除在每次窗口扩展时更新最大长度Python优化方案def lengthOfLongestSubstring(s: str) - int: char_set set() left max_len 0 for right in range(len(s)): while s[right] in char_set: char_set.remove(s[left]) left 1 char_set.add(s[right]) max_len max(max_len, right - left 1) return max_lenJava性能考量使用HashSet的contains方法检查重复注意Java的Set接口操作可能比Python稍慢可以进一步优化为使用字符数组实现O(1)时间检查public int lengthOfLongestSubstring(String s) { SetCharacter set new HashSet(); int left 0, maxLen 0; for (int right 0; right s.length(); right) { char c s.charAt(right); while (set.contains(c)) { set.remove(s.charAt(left)); } set.add(c); maxLen Math.max(maxLen, right - left 1); } return maxLen; }4. 长度最小的子数组LeetCode 209在正整数数组中寻找和≥target的最短连续子数组。算法选择对比方法时间复杂度空间复杂度适用场景暴力枚举O(n²)O(1)不推荐滑动窗口O(n)O(1)最优解前缀和二分O(nlogn)O(n)数组含负数时Python实现陷阱窗口和计算要放在收缩条件之后空数组和全数组特殊情况需要处理def minSubArrayLen(target: int, nums: List[int]) - int: left total 0 min_len float(inf) for right in range(len(nums)): total nums[right] while total target: min_len min(min_len, right - left 1) total - nums[left] left 1 return min_len if min_len ! float(inf) else 0Java边界处理整数溢出问题当target很大时使用Integer.MAX_VALUE表示初始最小值数组长度为0的特殊情况public int minSubArrayLen(int target, int[] nums) { int left 0, sum 0; int minLen Integer.MAX_VALUE; for (int right 0; right nums.length; right) { sum nums[right]; while (sum target) { minLen Math.min(minLen, right - left 1); sum - nums[left]; } } return minLen Integer.MAX_VALUE ? 0 : minLen; }5. 字符串的排列LeetCode 567判断字符串s2是否包含s1的排列即是否存在s2的子串是s1的某种排列。解题思路统计s1的字符频率维护一个与s1长度相同的滑动窗口比较窗口内字符频率是否与s1匹配Python计数器技巧from collections import Counter def checkInclusion(s1: str, s2: str) - bool: n1, n2 len(s1), len(s2) if n1 n2: return False target Counter(s1) window Counter(s2[:n1]) for i in range(n2 - n1): if window target: return True # 移动窗口 left_char s2[i] if window[left_char] 1: del window[left_char] else: window[left_char] - 1 right_char s2[i n1] window[right_char] window.get(right_char, 0) 1 return window targetJava数组优化使用固定长度数组替代哈希表利用字母ASCII值作为索引比较两个数组是否相等public boolean checkInclusion(String s1, String s2) { int n1 s1.length(), n2 s2.length(); if (n1 n2) return false; int[] target new int[26]; int[] window new int[26]; for (int i 0; i n1; i) { target[s1.charAt(i) - a]; window[s2.charAt(i) - a]; } for (int i 0; i n2 - n1; i) { if (Arrays.equals(target, window)) { return true; } // 移动窗口 window[s2.charAt(i) - a]--; window[s2.charAt(i n1) - a]; } return Arrays.equals(target, window); }6. 最大连续1的个数IIILeetCode 1004给定二进制数组可以将最多K个0翻转为1求最长连续1的子数组长度。问题转化 将问题转化为寻找包含最多K个0的最长子数组这正是滑动窗口的用武之地。Python实现关键维护窗口内0的计数当0计数超过K时收缩左边界每次扩展时更新最大长度def longestOnes(nums: List[int], k: int) - int: left zero_count max_len 0 for right in range(len(nums)): if nums[right] 0: zero_count 1 while zero_count k: if nums[left] 0: zero_count - 1 left 1 max_len max(max_len, right - left 1) return max_lenJava实现细节使用基本类型变量而非对象数组访问直接使用索引循环条件可以优化为while而非ifpublic int longestOnes(int[] nums, int k) { int left 0, zeroCount 0, maxLen 0; for (int right 0; right nums.length; right) { if (nums[right] 0) { zeroCount; } while (zeroCount k) { if (nums[left] 0) { zeroCount--; } left; } maxLen Math.max(maxLen, right - left 1); } return maxLen; }7. 滑动窗口算法面试避坑指南在实际面试中滑动窗口问题最容易出现的错误往往不是算法思路而是实现细节。以下是经过上百次面试实战总结的关键要点1. 窗口收缩条件不清晰明确什么情况下需要收缩窗口确保收缩条件与问题要求严格对应示例在最小覆盖子串中收缩条件是窗口已包含所有目标字符2. 边界条件处理不当空输入或特殊输入的处理窗口长度为0或1的情况目标值大于所有元素和的情况3. 更新结果的时机错误结果应该在窗口满足条件时更新对于最小化问题通常在收缩窗口时更新对于最大化问题通常在扩展窗口时更新4. 数据结构选择不当根据问题特点选择合适的数据结构字符频率统计defaultdict/HashMap或固定长度数组快速查找哈希集合有序统计TreeMap5. 语言特性忽视Python中defaultdict与普通dict的性能差异Java中HashMap的装箱/拆箱开销字符串操作的性能差异Java的substring较慢6. 复杂度分析不准确虽然滑动窗口是O(n)但内部操作可能有隐藏成本哈希表操作的平均O(1)与最坏O(n)区别字符串拼接在循环中的性能陷阱7. 变量命名混乱保持一致的命名规范left/right表示窗口边界window表示当前窗口状态min_len/max_len表示极值在实际编码时建议先写出算法框架再填充具体条件最后处理边界情况。例如# 1. 初始化窗口和状态变量 left 0 window {} result ... # 2. 主循环扩展窗口 for right in range(len(s)): # 更新窗口状态 ... # 3. 收缩窗口的条件 while ...: # 更新结果 ... # 移动左边界 ... # 4. 可能的其他更新 ... # 5. 返回最终结果 return result这种结构化的编码方式不仅能减少错误还能让面试官清晰地看到你的解题思路。记住在算法面试中清晰的代码结构有时比完美的解法更重要。

更多文章