题目
给定两个字符串 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 * 104s和p仅包含小写字母
思路
解法一: 滑动窗口+2个频次表+distance控制(自)
本解答参考76题的思路,只需要做以下1个改动:
distance==pLen时,停止滑动时,判断right-left==pLen,保证区间内的值恰好等于pLen的所有字符, 如果不满足,left指针继续往右移动;如果满足就计入答案。
代码一
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
sLen = len(s)
pLen = len(p)
if sLen == 0 or pLen == 0 or sLen < pLen:
return []
# 频次表
pFreq = [0 for _ in range(128)]
winFreq = [0 for _ in range(128)]
for c in p:
pFreq[ord(c)] += 1
# 滑动窗口模板
res = []
left = 0
right = 0
distance = 0
while right < sLen:
if winFreq[ord(s[right])] < pFreq[ord(s[right])]:
distance += 1
winFreq[ord(s[right])] += 1
right += 1
# 当频次相同,判断窗口长度与pLen是否相等,是的话就保存答案
while distance == pLen:
if right - left == pLen:
res.append(left)
if winFreq[ord(s[left])] == pFreq[ord(s[left])]:
distance -= 1
winFreq[ord(s[left])] -= 1
left += 1
return res
解法二: 定长的滑动窗口+2个频次表
对于解法一,虽然使用的是滑动窗口,但是题目有个条件没充分利用,那就是满足题意的窗口长度只可能等于pLen,解答一只是在筛选答案的时候利用上了,导致可能会出现很多次判断,其实可以让滑动窗口一开始就固定长度为pLen,这也是官方解答的第一个,值得借鉴。
具体方案:首先使用两个变量 sFreq,pFreq 计算s和p的各个字符出现次数,通过判断各个pLen窗口下 sFreq==pFreq,来判断滑动窗口是否满足条件
- 先遍历p和s的pLen长度窗口,统计sFreq和pFreq的字符出现次数,如果相等,那么说明第一个滑动窗口满足题意,也就是left从0开始的满足题意,0计入答案
- 右指针right从pLen开始,左指针left从0开始,每次left、right移动一个位置,然后right位置的s[right]计入sFreq中,同时将s[left]从sFreq中移除,再都判断 sFreq和pFreq是否相等,如果相等,将 left+1 计入答案
代码二
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
# 官解一
sLen = len(s)
pLen = len(p)
if sLen == 0 or pLen == 0 or sLen < pLen:
return []
res = []
# 频次表
pFreq = [0 for _ in range(26)]
sFreq = [0 for _ in range(26)]
for i in range(pLen):
pFreq[ord(p[i]) - 97] += 1
sFreq[ord(s[i]) - 97] += 1
if pFreq == sFreq:
res.append(0)
right = pLen
left = 0
while right < sLen:
sFreq[ord(s[left]) - 97] -= 1
sFreq[ord(s[right]) - 97] += 1
if sFreq == pFreq:
res.append(left+1)
right += 1
left += 1
return res
解法三: 定长的滑动窗口+1个频次表+differ变量判断是否相等
官解2:对于解法二的2个频次表,其实可以更加优化,使用一个频次表 freq 来简化空间复杂度。具体步骤如下:
- 先遍历s和p的pLen长度,s位置的字符频数+1, p位置的字符频数的-1,然后查看
freq各个位置上>0的值有多少,记为differ(但是不能计算sum,因为可能存在某个元素多了一个,但是某个元素少了一个,导致sum=0),如果differ==0,说明0位置也满足条件,计入答案中 right指针从pLen开始,left指针从0开始,计入right指针,移除left指针,每次同时移动一个位置,保证滑动窗口的长度,判断differ:- 移除
left指针时,因为是移除,减掉该字符,考虑differ何时应该变化,移除前,如果freq[s[left]]的元素刚好=1,那么这次移除后,该字符刚好满足,differ-=1;如果该字符之前就是0满足条件,现在要移除的话,字符又不满足条件了,differ+1 - 加入
right指针时,因为是加入,加上该字符,考虑differ何时应该变化,加入前,如果freq[s[right]]的元素刚好=-1,那么这次加入后,该字符刚好满足,differ-=1;如果该字符之前就是0满足条件,现在要加入的话,字符又不满足条件了,differ+1
- 移除
- 如果窗口往右移动完成后,differ=0,那么说明left的移除和right的加入,又是一个满足条件的,那么left+1应该计入答案。
代码三: 统计freq的元素>0的differ
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
sLen = len(s)
pLen = len(p)
if sLen == 0 or pLen == 0 or sLen < pLen:
return []
res = []
# 频次表
freq = [0 for _ in range(26)]
for i in range(pLen):
freq[ord(p[i]) - 97] -= 1
freq[ord(s[i]) - 97] += 1
differ = [c!=0 for c in freq].count(True)
if differ == 0:
res.append(0)
left = 0
right = pLen
while right < sLen:
if freq[ord(s[left])-97] == 1:
differ -= 1
if freq[ord(s[left]) - 97] == 0:
differ += 1
freq[ord(s[left]) - 97] -= 1
if freq[ord(s[right])-97] == -1:
differ -= 1
if freq[ord(s[right]) - 97] == 0:
differ += 1
freq[ord(s[right]) - 97] += 1
if differ == 0:
res.append(left+1)
left += 1
right += 1
return res