题目描述
找出 s 中所有是 p 的字母异位词的子串,返回它们的起始下标。异位词 = 字母种类和个数都一样、只是顺序不同。
s = cbaebabacd
p = abc
输出 = [0, 6]
思路解析
每个起点都排序比一次能做,但每次都重头排,重复劳动太多。
窗口长度永远等于 len(p)。窗口右移一格,就是右边进一个字母、左边出一个字母,计数 O(1) 更新,不用重排。
维护一张「窗口里每个字母多少个」的表,只要它和 p 的计数表完全一样,当前窗口就是一个异位词。
win=[0,2]:第一个长度 3 的窗口 cba,计数和 abc 一模一样,命中!记下起点 0。
win=[1,3]:窗口右滑一格变成 bae,进来个 e、出去个 c。p 里压根没有 e,不匹配,跳过。
win 继续滑动:一路滑过去,中间这些窗口字母计数都跟 abc 对不上,继续往右。
win=[6,8]:滑到下标 6 的 bac,计数又和 abc 对上了,命中!记下起点 6。最终答案 [0, 6]。
定长滑窗比变长还简单:右指针每进一格,左指针就跟着出一格,窗口大小焊死,每步比一次计数。
参考代码(Python)
Python
1from collections import Counter
2def findAnagrams(s, p):
3 need = Counter(p); win = Counter(); k = len(p)
4 res = []
5 for r, ch in enumerate(s):
6 win[ch] += 1 # 右进
7 if r >= k: # 维持定长
8 left = s[r - k]
9 win[left] -= 1 # 左出
10 if win[left] == 0: del win[left]
11 if win == need: res.append(r - k + 1)
12 return res复杂度分析
- 时间复杂度:O(n) —— 窗口滑一遍,计数 O(1)
- 空间复杂度:O(1) —— 最多 26 个字母
套路模板
定长窗口的骨架:右进、超长则左出、窗口满了就结算。把「移除 nums[r-k]」这步焊牢,窗口大小就稳了。
Python
1for r in range(n):
2 加入 nums[r]
3 if r >= k: # 超长就吐掉最左
4 移除 nums[r - k]
5 if r >= k - 1: # 窗口已满,结算
6 更新答案易错点
- ❌ 窗口起点算成 r → ✅ 起点是 r - k + 1(窗口是 [r-k+1, r] 这一段,左端才是要记的起始下标)
- ❌ win[x] 减到 0 还留在表里 → ✅ 减到 0 就 del 掉(不删的话 win 里残留 {x:0},和 need 直接比较会不相等)
- ❌ 每步都重新统计整个窗口 → ✅ 只更新进/出的那一个字母(重新统计就退化成 O(n·k) 了,定长滑窗的优势全没了)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。