1. 问题描述:
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 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" 的字母异位词。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string
2. 思路分析:
这道题目属于经典的双指针算法题目,分析题目可以知道字母异位词需要满足两个条件:
- 子串长度子字符串p的长度是相等的
- 子串的字母出现的次数与字符串p出现的字符次数是相等的
所以我们使用两个指针来维护长度为k的区间,k为字符串p的长度,这样可以满足第一个条件,对于第二个条件我们其实是可以使用哈希表来解决的,一开始的时候将字符串p出现的字符次数存储到哈希表中,我们在维护长度为k的区间的时候更新哈希表,如果当前的字符在哈希表中存在那么哈希表中对应的字符数减1,说明当前这种字符需要匹配的字符数目减1,如果某种字符数减为0之后说明当前种类的字符数目满足要求了(这里使用到的一个技巧就是使用一个变量satisfy来记录满足要求的字符种类数目),所以我们在维护长度为k的区间的时候结合哈希表记录一下当前满足字符串p的种类数目satisfy,如果当前的区间长度大于了k之后那么就需要将指针j往右移动,使得区间的长度减小,也即删除掉区间最左边的字符,如果最左边的字符在哈希表中的次数为0说明删除掉当前字符之后满足要求的字母种类是减1的,并且需要更新哈希表中当前字符的匹配数目,也即相应的字符次数要加1,表示当前匹配的字符次数需要多一个。整个过程就是维护长度为k的区间的过程,维护的时候更新哈希表和记录当前匹配的字母种类的变量值,只有当哈希表中所有字符次数减为0而且满足要求的字母种类数目staisfy等于哈希表大小的时候说明满足一种字母异位词。
3. 代码如下:
from typing import List
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
dic = dict()
# 将字母出现的次数存储到字典中, 表示字母异位词必须满足字母出现的次数
for c in p:
if c in dic:
dic[c] += 1
else:
dic[c] = 1
n = len(s)
i, j = 0, 0
staisfy = 0
res = list()
# 其实每一次维护的是长度为k的窗口, k = len(p)
while i < n:
c = s[i]
# 当前需要匹配的字母c的次数减1
if c in dic: dic[c] -= 1
# 当前这个字母的次数满足要求
if c in dic and dic[c] == 0:
staisfy += 1
while i - j + 1 > len(p):
# 删除最左边的字符如果删除的字符刚好是满足种类的数目那么satisfy需要减1
if s[j] in dic and dic[s[j]] == 0: staisfy -= 1
if s[j] in dic:
dic[s[j]] += 1
j += 1
# 注意这里是字典的大小
if staisfy == len(dic): res.append(j)
i += 1
return res