【算法专题】滑动窗口:从"无重复字符"到"字母异位词"的深度剖析

我的主页: 寻星探路
个人专栏: 《JAVA(SE)----如此简单!!! 》 《从青铜到王者,就差这讲数据结构!!!》
《数据库那些事!!!》 《JavaEE 初阶启程记:跟我走不踩坑》
《JavaEE 进阶:从架构到落地实战 》 《测试开发漫谈》
《测开视角・力扣算法通关》 《从 0 到 1 刷力扣:算法 + 代码双提升》
《Python 全栈测试开发之路》
没有人天生就会编程,但我生来倔强!!!
寻星探路的个人简介:


1. 题目一:无重复字符的最长子串 (LeetCode 3)
题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

解题思路:变长滑动窗口
这道题的核心在于维护一个不包含重复字符的窗口。
- 使用两个指针(左指针
i,右指针rk)表示窗口边界。 - 随着右指针向右移动,将字符存入
HashSet。 - 如果遇到重复字符,则左指针开始向右移动,并从集合中删除字符,直到窗口内不再有该重复字符。
Java 代码实现
java
class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,用于记录窗口内出现的字符
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针 rk 初始值为 -1,表示在字符串左边界的左侧
int rk = -1, ans = 0;
for (int i = 0; i < n; i++) {
if (i != 0) {
// 每次左指针右移一位,就从集合中移除上一个左边界的字符
occ.remove(s.charAt(i - 1));
}
// 尝试不断移动右指针
while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
// 如果下一个字符不重复,加入集合,右指针右移
occ.add(s.charAt(rk + 1));
rk++;
}
// 更新最大长度:当前窗口为 [i, rk]
ans = Math.max(ans, rk - i + 1);
}
return ans;
}
}
2. 题目二:找到字符串中所有字母异位词 (LeetCode 438)
题目描述
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。

异位词:由相同字母重排列形成的字符串(字母种类和数量完全一致)。
解题思路:固定长度滑动窗口
因为异位词的长度必须等于 p.length(),所以这是一个固定窗口大小的问题。
- 使用数组(或 HashMap)统计
p中各字母出现的频次。 - 维护一个长度为
p.length()的窗口,统计窗口内s的字母频次。 - 比较两个频次数组是否相等。若相等,则当前窗口起始索引即为答案。
Java 代码实现
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
if (sLen < pLen) return new ArrayList<>();
List<Integer> ans = new ArrayList<>();
// 使用 26 位数组记录 a-z 频次,空间复杂度 O(1)
int[] sCount = new int[26];
int[] pCount = new int[26];
// 1. 初始化:统计 p 的频次和 s 第一个窗口的频次
for (int i = 0; i < pLen; i++) {
sCount[s.charAt(i) - 'a']++;
pCount[p.charAt(i) - 'a']++;
}
// 检查第一个窗口
if (Arrays.equals(sCount, pCount)) ans.add(0);
// 2. 固定长度窗口向右滑动
for (int i = 0; i < sLen - pLen; i++) {
sCount[s.charAt(i) - 'a']--; // 移除左边界字符
sCount[s.charAt(i + pLen) - 'a']++; // 移入右边界新字符
// 每次滑动后对比频次
if (Arrays.equals(sCount, pCount)) {
ans.add(i + 1);
}
}
return ans;
}
}
3. 深度拓展:滑动窗口的进阶技巧
优化 1:双数组对比 vs 变量维护
在 438 题中,我们使用了 Arrays.equals,其时间复杂度为 。
进阶写法 :可以维护一个变量 differ 记录两个频次数组中差异的数量。只有当 differ == 0 时才满足条件。这样可以将对比复杂度从 优化到 。
优化 2:ASCII 码的应用
如果题目中字符不仅限于小写字母(如包含空格、数字),应使用 int[128] 或 HashMap<Character, Integer> 来存储频次。
4. 总结:滑动窗口类题目的思考模型
遇到**"子串"、"连续子数组"**等字眼时,应第一时间想到滑动窗口。具体思考流程如下:
- 确定窗口类型:
- 固定窗口:窗口大小已知(如 438 题)。只需维护右入左出,并判断窗口状态。
- 变长窗口:窗口大小随条件变化(如 3 题)。通常是右指针先动寻找可行解,左指针再动寻找最优解。
- 确定数据结构:
- 需要判断"是否存在":用
Set。 - 需要统计"出现次数":用
int[26]数组或Map。
- 确定转移逻辑:
- 入窗 :
right指针移动,更新窗口内状态。 - 收缩 :什么条件下
left指针需要右移? - 更新 :在什么位置记录结果(
ans)?
希望这篇总结对你有帮助!如果你正在准备面试,建议把这两道题对比着多写几遍,体会"左右指针"协同工作的美感。