图解题库 / 滑动窗口 · 定长

438. 找到字符串中所有字母异位词

中等 含交互动画

学完这道你应该能: 说清它为什么用「定长」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

找出 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) 了,定长滑窗的优势全没了)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →56. 合并区间 ← 返回题库