1:题目描述(力扣)
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
2:解题思路
因为字符串s和字符串p中的都仅包含小写字母,小写字母的数量是有限的,为26个,因此我们考虑使用哈希结构-数组来解决该题。
第一种解法:滑动窗口+数组
思路:
求的是p在s中的子串,所以可以统计出字符串p中的字母数量,然后遍历去统计字符串s中符合长度的子串中字母的数量, 判断子串中字母数量是否与p中字母数量一致,一致则将子串的起始位置加入列表中,不一致,则向后移动子串的起始位置,此时需要减去上一个子串的起始位置的字母数量。依次循环,直到最后一个符合长度的子串。
具体步骤如下:
第一步:定义一个列表res=[],用来存放满足条件的子串起始位置,初始化为空数组;定义一个变量s_len=len(s),定义一个变量p_len=len(p)。
第二步:先判断字符串s的长度是否小于字符串p,小于则s不可能包含有p的字母异位词的子串,直接返回res。
第三步:定义一个数组s_count,长度为26,初始值均为0,用来存储s中子串的字母出现数量;
定义一个数组p_count,长度为26,初始值均为0,用来存储p中字母出现的数量。
第四步:使用for循环遍历字符串p和s中从0开始长度为p_len的子串,统计p中字母出现的数量,统计子串中字母出现的数量
以示例一为例:
第五步:判断数组s_count和数组p_count内的元素是否相等,相等,则将子串的起始位置下标0加入到res中。
第六步:使用for循环遍历字符串s,以子串的结束位置下标为循环条件,从第一个子串的结束位置(即字符串p的长度)开始,到字符串s的长度s_len结束;因为子串的起始位置已经向后移动一位了,所以我们需要在s_count中,减去前一个子串的起始位置字母的数量,再加上现在子串的结束位置的字母数量;然后比较s_count和p_count中的元素是否一致,一致则将子串的起始位置加入res,不一致则进行下一次循环。
第七步:循环字符串s结束后,返回res。
代码展示:
class Solution:
def findAnagrams(self, s, p):
s_len, p_len = len(s), len(p)
# 当字符串s的长度小于字符串p的长度,s中不可能包含p中的子串,返回空数组
if s_len < p_len:
return []
res = []
s_count = [0] * 26
p_count = [0] * 26
# 先将p字符串中包含的字母数量统计出来,并且同时统计字符串s中,从下标0开始,与p长度相同的子串中字母的数量
for i in range(p_len):
s_count[ord(s[i]) - ord("a")] += 1
p_count[ord(p[i]) - ord("a")] += 1
# 判断两个数组是否相等,相等则将下标0加入到res中
if s_count == p_count:
res.append(0)
# 以长度为p_len为准,每次向后一位移动,i表示最后一个字符的位置
for i in range(p_len, s_len):
# 向后滑动一位,需要前面的字母对应的数量,减掉
s_count[ord(s[i-p_len]) - ord("a")] -= 1
# 滑动后,需要把最后一个字符串的数量加上
s_count[ord(s[i]) - ord("a")] += 1
# 判断两个数组是否相等
if s_count == p_count:
# 相等则将该子串的起始位置下标加入res,子串起始下标为i-p_len+1
res.append(i-p_len+1)
return res
第二种解法:滑动窗口+双指针+数组
用双指针来表示滑动窗口的两侧边界,当滑动窗口的长度等于p的长度时,表示找到一个异位词。
思路:
定义滑动窗口的左右两个指针left,right
right一步一步向右走遍历s字符串
right当前遍历到的字符加入s_count后不满足p_count的字符数量要求,将滑动窗口左侧字符不断弹出,也就是left不断右移,直到符合要求为止。
当滑动窗口的长度等于p的长度时,这时的s子字符串就是p的异位词。
其中,left和right表示滑动窗口在字符串s中的索引,s_left和s_right表示字符串s中索引为left和right的字符在数组中的索引
代码展示:
# 滑动窗口+双指针
class Solution:
def findAnagrams(self, s, p):
s_len, p_len, res = len(s), len(p), []
# 当s的长度小于p时,返回空数组
if s_len < p_len:
return res
p_count = [0] * 26
s_count = [0] * 26
# 统计p中字母的数量
for i in range(p_len):
p_count[ord(p[i]) - ord("a")] += 1
# left为左指针,表示子串的起始位置,初始值为0
left = 0
# right为右指针,表示子串的结束位置
for right in range(s_len):
# 统计子串最后一个字母的数量
s_right = ord(s[right]) - ord("a")
s_count[s_right] += 1
# 当遍历到的字母索引的数量大于p中对应索引的数量时,说明包含这个字母的子串都不满足要求,则需要将左边的元素一个一个的弹出
while s_count[s_right] > p_count[s_right]:
s_left = ord(s[left]) - ord("a")
s_count[s_left] -= 1
# 每减去左指针对应的字母索引的数量,需要向后移动一位
left += 1
# 当right与left包含的字母长度等于p的长度时,为满足要求的子串,将子串的起始位置left加入res
if right-left+1 == p_len:
res.append(left)
return res