跳到正文
第 438 题中等

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

Find All Anagrams in a String

在字符串 s 中找出所有长度为 p.length 的异位词子串,返回它们的起始下标。

在力扣中练习

题目描述

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

 

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

 示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

 

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

解题思路

主解法

定长滑动窗口 · 字符计数

窗口每右移一格,只加一个新字符、删一个旧字符

异位词的长度一定与 p 相同,所以只需要检查所有长度为 m 的窗口。

每个窗口都重新排序是 O(n · m log m)。改成计数:用两个长度 26 的数组分别记录 p 和当前窗口的字符次数,窗口右移时只更新两个位置。

自拟主例 s = "ababac"、p = "aba":

  • 窗口 [0,2] 是 aba,计数一致,记录 0;
  • 右移成 bab,count 不一致;
  • 再右移成 aba,记录 2;
  • 最后 bac,不一致。
分步图解

先统计 p 的字符数,再看 s 的前 3 个字符窗口;计数一致,起点 0 成立。

m
3
s
0
1
2
3
4
5
a
b
a
b
a
c
固定长度窗口 [0, 2]
result
0
异位词窗口的起点
need / window
a→2 / 2
b→1 / 1
字符:目标次数 / 窗口内次数
1 / 3
前 3 个字符就是 aba,起点 0 命中;窗口右移一格后变成 bab,计数不再一致。

窗口大小固定,左右指针同步移动,不需要内层收缩循环。时间 O(26 · n),26 是常数,视作 O(n);空间 O(1)。

分步图解

窗口右移到 [2, 4]:加入 'a'、移出 'b';计数一致,记录起点 2。

left
2
right
4
s
0
1
2
3
4
5
a
b
a
b
a
c
固定长度窗口 [2, 4]
result
02
异位词窗口的起点
need / window
a→2 / 2
b→1 / 1
字符:目标次数 / 窗口内次数
1 / 3
窗口再次回到 aba,记录起点 2;最后滑到 bac,计数不匹配。

如果不想每次比较整个计数数组,可以额外维护一个 diff 变量,记录有多少个字符的次数不匹配,把比较降到 O(1)。

时间 O(26 · n) = O(n)空间 O(1)

易错点

  • 窗口长度写成 m+1:异位词长度必须与 p 相同。
  • 加入和移除的顺序无所谓,但比较必须放在两者都完成之后。
  • 记录起点写成 i:窗口起点是 i-m+1,不是右端点。
  • m > n 时仍访问 s[i]:需要先返回空结果。

代码与演示

演示输入s = "ababac", p = "aba"

先统计 p 的字符数,再看 s 的前 3 个字符窗口;计数一致,起点 0 成立。

m
3
s
0
1
2
3
4
5
a
b
a
b
a
c
固定长度窗口 [0, 2]
result
0
异位词窗口的起点
need / window
a→2 / 2
b→1 / 1
字符:目标次数 / 窗口内次数
1 / 5
查看 C++ 代码

延伸阅读

  • 定长窗口的题目通常都可以用“进入一个、离开一个”的增量更新,避免每步重算整段。
  • 如果字符集不是小写字母,把 26 改成哈希表;比较时可以用 diff 计数保持 O(1)。