玩转 LeetCode Hot 100:滑动窗口两大万能解题模板与深度拆解
2026/6/13 20:38:39 网站建设 项目流程

算法应用场景

在算法面试中,滑动窗口(Sliding Window)是解决连续子数组/子串问题的终极杀招。它的核心魅力在于:将原本需要两层for循环暴力枚举的 $O(n^2)$ 复杂度,直接优化到 $O(n)$。

很多初学者在刷 Hot 100 时,常常被窗口的扩大、缩小、何时记录答案搞得头晕眼花。其实,滑动窗口的题目在底层只有两种标准形态:「可变窗口模板(求最长/最短)」「固定窗口模板(求特定长度)」

今天我们依然用最纯粹的while循环、基础数组和if-else逻辑,帮你彻底拉通滑动窗口的底层逻辑。

🚀 模板一:可变窗口模板(求最长/最短子串)

1. 模板抽象与核心思想

这类题目通常要求找一个满足某种条件的最长或最短连续区间。它的核心思想是:右指针(right)负责拼命向右移动扩大窗口,用来寻找可行解;一旦窗口内满足/不满足条件了,左指针(left)就迫不得已向右移动缩小窗口,用来优化答案或恢复合法状态。

// 通用基础款代码模板 int left = 0; int maxLength = 0; // 如果求最短,则初始化为 Integer.MAX_VALUE // 1. 右指针主动作为主循环,不断扩大窗口 for (int right = 0; right < s.length(); right++) { char c = s.charAt(right); // 进窗:把当前右指针的数据加入窗口的状态统计中 更新窗口状态(c); // 2. 触发特定条件,左指针被迫收缩窗口 // 情况 A(求最长,如无重复字符):当窗口内出现重复,while 持续收缩直到没有重复 // 情况 B(求最短,如覆盖子串):当窗口内已经满足条件,while 持续收缩寻找最小窗口 while (窗口需要收缩的条件) { char d = s.charAt(left); // 出窗:把当前左指针的数据从窗口状态中移除 移除窗口状态(d); left++; // 左指针右移,缩小窗口 } // 3. 在合法的时机更新我们的最终答案 // 求最长通常在这里更新(因为此时 while 刚退出,窗口一定合法) maxLength = Math.max(maxLength, right - left + 1); }

2. Hot 100 实战演练:LeetCode 3. 无重复字符的最长子串

题目大意:给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度。

🛠️ 纯基础款代码实现

我们直接用一个最基础的频次数组int[] count = new int[128]来充当窗口状态。一旦某个字符的频次大于 1,说明进窗后重复了,左指针必须立刻收缩。

class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; // 用最基础的数组记录窗口内每个字符出现的次数 int[] count = new int[128]; int left = 0; int maxLength = 0; // 右指针不断扩大窗口 for (int right = 0; right < s.length(); right++) { char rightChar = s.charAt(right); // 进窗:当前字符数量加 1 count[rightChar]++; // 核心收缩条件:如果当前字符的数量大于 1,说明它在窗口里重复了! while (count[rightChar] > 1) { char leftChar = s.charAt(left); // 出窗:左指针字符数量减 1 count[leftChar]--; // 左指针右移 left++; } // 走到这里,窗口内一定没有重复字符,放心地更新最大长度 int currentLength = right - left + 1; if (currentLength > maxLength) { maxLength = currentLength; } } return maxLength; } }

🚀 模板二:固定窗口模板(求特定长度区间)

1. 模板抽象与核心思想

这类题目的特征非常明显:题目明确给出了窗口的固定大小 $K$(比如找大小为 $K$ 的子数组的最大和,或者找所有字母异位词的起始索引)。

它的核心思想是:右指针先往前走,把窗口撑满到固定大小。此后,每当右指针往前走一步(新元素进窗),左指针也必须同步往前走一步(旧元素出窗),保持窗口大小永远不变,就像一条固定长度的贪吃蛇在数组上向前蠕动。

// 通用基础款代码模板 int left = 0; int windowSize = K; // 题目要求的固定大小 for (int right = 0; right < nums.length; right++) { // 1. 新元素进窗 更新窗口状态(nums[right]); // 2. 还没达到固定窗口大小时,先不处理,继续让右指针把窗撑大 if (right - left + 1 < windowSize) { continue; } // 3. 走到这里,窗口大小正好等于 windowSize。触发答案比对 if (窗口状态满足要求) { 记录答案; } // 4. 贪吃蛇蠕动:为了让右指针下一步能进窗,左指针现在必须出窗 移除窗口状态(nums[left]); left++; }

2. Hot 100 实战演练:LeetCode 438. 找到字符串中所有字母异位词

题目大意:给定两个字符串sp,找到s中所有p字母异位词的子串,返回这些子串的起始索引。字母异位词指字母相同但顺序不同的字符串。

🛠️ 纯基础款代码实现

因为字母异位词的长度必须和p完全一样,所以这就是一个固定长度为p.length()的窗口问题。我们用两个 26 位的数组比对频次即可。

class Solution { public List<Integer> findAnagrams(String s, String p) { List<Integer> result = new ArrayList<>(); if (s.length() < p.length()) return result; // 1. 统计目标字符串 p 的字符频次 int[] pCount = new int[26]; for (int i = 0; i < p.length(); i++) { pCount[p.charAt(i) - 'a']++; } // 2. 初始化滑动窗口的频次统计 int[] sCount = new int[26]; int left = 0; int windowSize = p.length(); // 固定窗口大小 for (int right = 0; right < s.length(); right++) { // 新元素进窗 sCount[s.charAt(right) - 'a']++; // 如果窗口大小还没攒够,继续让右指针往前走 if (right - left + 1 < windowSize) { continue; } // 3. 窗口大小正好相等了,开始比对两个数组是否完全一致 if (isSame(sCount, pCount)) { result.add(left); // 匹配成功,记录左边界索引 } // 4. 窗口要往前移动了,左边界字符出窗 sCount[s.charAt(left) - 'a']--; left++; } return result; } // 纯基础款对比辅助方法:判断两个频次数组是否完全一样 private boolean isSame(int[] arr1, int[] arr2) { for (int i = 0; i < 26; i++) { if (arr1[i] != arr2[i]) { return false; } } return true; } }

🛠️ 总结:滑动窗口解题思考流

拿到一道连续子数组/子串的题目,如何迅速破局?

  1. 关键词识别:只要题目中出现“连续子数组”“连续子串”“最长/最短满足...的区间”,不要犹豫,90% 概率掏出滑动窗口。

  2. 判断窗口类型

    • 题目要求“最长/最短是多少”,套用模板一(可变窗口)。核心去想:什么时候该让左指针收缩?

    • 题目要求“长度必须等于固定值,套用模板二(固定窗口)。核心去想:怎么在保持right - left + 1 == K的前提下,让左右指针一起往前蠕动?

  3. 状态监控器的选择

    • 字符范围有限(如纯小写字母或 ASCII 码),优先用int[]数组作为窗口的状态记录器,性能最高。

    • 数据范围很大或离散,再考虑引入HashMapHashSet

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询