一、题目描述
给定两个字符串 s 和 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
- 字母异位词:指由相同字母重排列形成的字符串(字符种类和频次完全相同,顺序无关)。
- 输入示例:
s = "cbaebabacd",p = "abc" - 输出示例:
[0, 6] - 解释:
s中索引 0 的子串"cba"、索引 6 的子串"bac"都是"abc"的字母异位词。
二、解题思路:固定长度滑动窗口 + 数组计数
核心逻辑(循序渐进版)
- 问题拆解:要找所有长度等于
len(p)的子串,且子串与p字符频次完全一致。 - 暴力解法痛点:枚举所有长度为
len(p)的子串,逐个统计频次对比,时间复杂度 O (n*m)(n=len (s), m=len (p)),效率低。 - 滑动窗口优化:用双指针维护固定长度的窗口,滑动时仅更新「进入窗口」和「离开窗口」的字符频次,避免重复统计,时间复杂度降至 O (n)。
- 数组计数选型:因仅涉及小写字母,用长度 26 的数组替代哈希表,查询 / 更新速度更快,且可直接通过
==对比频次。
具体步骤
- 步骤 1:初始化两个长度为 26 的数组,分别统计
p的字符频次p_count。 - 步骤 2:用右指针遍历
s,将字符加入窗口并更新s_count。 - 步骤 3:当窗口长度超过
len(p)时,左指针右移,移出左侧字符并更新s_count。 - 步骤 4:窗口长度等于
len(p)时,对比s_count和p_count,相同则记录左指针索引。
class Solution:
def findAnagrams(self, s: str, p: str) -> list[int]:
len_p = len(p)
len_s = len(s) # 边界处理:p比s长,直接返回空列表
if len_p > len_s:
return []
res = []
# 初始化26位字母频次数组(对应a-z)
p_count = [0] * 26
s_count = [0] * 26
# 统计p的字符频次
for char in p:
# 转换为0-25的索引(a->0, b->1...)
p_count[ord(char) - ord('a')] += 1
# 初始化第一个窗口(前len_p个字符)
for i in range(len_p):
s_count[ord(s[i]) - ord('a')] += 1
# 检查第一个窗口是否匹配
if s_count == p_count:
res.append(0)
# 滑动窗口遍历剩余字符
for right in range(len_p, len_s):
# 右指针:新字符进入窗口,频次+1
in_char = s[right]
s_count[ord(in_char) - ord('a')] += 1
# 左指针:旧字符离开窗口,频次-1
out_char = s[right - len_p]
s_count[ord(out_char) - ord('a')] -= 1
# 检查当前窗口是否匹配,匹配则记录起始索引
if s_count == p_count:
res.append(right - len_p + 1)
return res
四、核心知识点深度解析
1. 固定长度滑动窗口
- 适用场景:需要找固定长度的子串 / 子数组(如本题找长度 = len (p) 的异位词)。
- 核心优势:双指针同向移动,无回溯,线性时间复杂度。
- 对比可变窗口:可变窗口(如 LC3 无重复最长子串)需动态调整窗口大小,固定窗口只需维护边界滑动。
2. 数组计数 vs 哈希表计数
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 数组计数 | 速度快、可直接对比 | 仅支持有限字符集 | 小写字母 / ASCII 字符 |
| 哈希表计数 | 支持任意字符集 | 对比需遍历键值对 | 包含特殊字符 / 大小写混合 |
五、扩展知识(提升文章吸引力)
1. 性能优化:diff 变量减少对比开销
上述代码每次对比两个数组需 O (26) 时间,可引入diff变量记录频次不一致的字符数:
# 优化版核心逻辑(仅展示关键部分)
diff = 0 # 初始化diff:统计频次不同的字符数
for i in range(26):
if p_count[i] != s_count[i]:
diff += 1
# 滑动时仅更新diff
if diff == 0:
res.append(...) # 无差异则匹配
优化后对比操作从 O (26) 降至 O (1),常数时间更优。
2. 同类题目联动
- LC567. 字符串的排列(判断是否存在异位词子串)
- LC76. 最小覆盖子串(可变窗口 + 哈希表)
- LC3. 无重复字符的最长子串(可变窗口)
总结
- 本题核心是固定长度滑动窗口 + 26 位数组计数,将暴力 O (n*m) 优化为 O (n);
- 数组计数仅适用于有限字符集,对比哈希表更高效,可直接用
==判断频次是否一致; - 窗口滑动时需注意「进入 / 离开」字符的频次更新,以及起始索引的正确计算。