两种方法:定长滑窗/不定长滑窗
目标:找出字符串 s
中所有 p
的字母异位词的起始索引。
核心思路是:滑动窗口 + 哈希表比较字符频次
定义两个字典(collections.Counter
):
cnt_p
:统计字符串 p
中每个字符出现的次数。
cnt_s
:用来统计 s
中滑动窗口内的字符频次。
使用滑动窗口遍历 s
:
每次向右滑动一位,记录当前字符(c
)的频率到 cnt_s
中。
left = right - len(p) + 1
是当前滑动窗口的左边界。
如果 left < 0
,说明窗口长度还不够,跳过本次比较。
若当前窗口内字符频率与 p
的字符频率相同(cnt_s == cnt_p
),说明这是一个异位词的起始位置,加入结果列表。
移除窗口最左边的字符计数(cnt_s[s[left]] -= 1
),为下次滑动窗口做准备。
# 请使用 Python3 提交代码!Python2 已经被淘汰了
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
ans = []
cnt_p = Counter(p) # 统计 p 的每种字母的出现次数
cnt_s = Counter() # 统计 s 的长为 len(p) 的子串 s' 的每种字母的出现次数
for right, c in enumerate(s):
cnt_s[c] += 1 # 右端点字母进入窗口
left = right - len(p) + 1
if left < 0: # 窗口长度不足 len(p)
continue
if cnt_s == cnt_p: # s' 和 p 的每种字母的出现次数都相同
ans.append(left) # s' 左端点下标加入答案
cnt_s[s[left]] -= 1 # 左端点字母离开窗口
return ans
在一个窗口内,如果字母出现次数相同,那么就判断是字母异位词;
设:
n
是字符串 s
的长度;
m
是字符串 p
的长度;
a
是字符集大小(最多 26 个小写字母)。
每个字符只进入和离开窗口一次:共 O(n)
。
每次比较 cnt_s == cnt_p
:
在 Python 中,Counter
的比较会遍历内部 key。
最坏情况下是 O(a)
,即 26 个小写字母。
总体为:O(n * a)
,在本题中 a = 26
是常数,因此可以认为是 O(n)
。
cnt_p
和 cnt_s
都是 Counter
对象,最多存储 26 个字母的频次。
所以:空间复杂度是 O(a)
= O(26)
= O(1)
(常数级)
ans
最多包含 n - m + 1
个索引,最坏为 O(n)
。
O(n)
(主要是结果数组,计数器是常数空间)在字符串 s
中查找所有与字符串 p
是字母异位词的子串起始索引。
使用 滑动窗口 + Counter 频次计数表;
与前一个版本不同的是:只使用一个计数器 cnt
,而非同时维护两个窗口计数器;
在窗口中每加入一个字符,就将 cnt[c] -= 1
;
如果某个字符计数小于 0,说明此字符出现次数过多,则不断收缩左边界(恢复字符计数);
当窗口长度刚好等于 p
长度时,表示当前窗口是合法的异位词,记录其起始位置。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
ans = []
cnt = Counter(p) # 统计 p 的每种字母的出现次数
left = 0
for right, c in enumerate(s):
cnt[c] -= 1 # 右端点字母进入窗口
while cnt[c] < 0: # 字母 c 太多了
cnt[s[left]] += 1 # 左端点字母离开窗口
left += 1
if right - left + 1 == len(p): # s' 和 p 的每种字母的出现次数都相同
ans.append(left) # s' 左端点下标加入答案
return ans
s = "cbaebabacd", p = "abc"
初始化 cnt = Counter({'a':1, 'b':1, 'c':1})
当遍历到前三个字符 "cba"
时,窗口长度等于 3,且所有 cnt
中字符都被消耗为 0,说明是一个异位词 → 加入答案。
如果某字符被多加了(cnt[c] < 0
),就从左侧不断弹出直到窗口合法。
设:
n = len(s)
,字符串 s 的长度;
m = len(p)
,字符串 p 的长度;
a = 26
,英文小写字母数量。
遍历一次 s
,每个字符至多进入窗口一次,离开窗口一次;
每个字符进入/退出窗口都只涉及对 cnt
的简单加减操作,是 O(1)
;
整体的操作是 线性的扫描和窗口移动。
O(n)
cnt
最多维护 26 个小写字母的计数;
ans
最多存储 O(n)
个索引位置。
O(1)
(cnt
是常数空间)+ O(n)
(结果数组)
项目 | 本版本(单 Counter + 窗口平衡) | 前版本(双 Counter 比较) |
---|---|---|
比较逻辑 | 巧妙用计数平衡维护合法窗口 | 每轮完整比较两个 Counter |
时间复杂度 | O(n) (无需逐字符比较) |
O(n * a) (每轮比较 26 字符) |
空间复杂度 | O(1) + O(n) |
O(1) + O(n) |
优势 | 更快,避免多余字典比较 | 结构直观,便于理解 |