给定一个字符串 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" 的字母异位词。
解题思路
首先想到的解法,就是将p
的所有组合存到一个数组p_per
中,接着扫描s
,判断s[i:i + p_len]
是否在这个数组中。
class Solution:
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
import itertools
result = []
p_len, s_len = len(p), len(s)
if s_len < p_len:
return result
p_per = []
for i in itertools.permutations(p): #通过itertools中的permutations产生所有组合
p_per.append(''.join(i))
for i in range(s_len):
if i + p_len <= s_len and s[i:i + p_len] in p_per:
result.append(i)
return result
但是这种解法的空间复杂度很高O(per(p)*len(p))
。而且如果输入s
和p
都非常长的情况会出现超时,因为p
的组合数太多了。
那么我们有没有即快速又节省空间的算法呢?
其实我们通过分析发现,所谓的字母异位词
,只要保证p中的各个字母个数
在s的字串中
相同即可。
class Solution:
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
from collections import Counter
s_len, p_len = len(s), len(p)
pChar, sChar = Counter(p), Counter()
result = []
for i in range(s_len):
sChar[s[i]] += 1
if i >= p_len:
sChar[s[i - p_len]] -= 1
if sChar[s[i - p_len]] == 0:
del sChar[s[i - p_len]]
if pChar == sChar:
result.append(i - p_len + 1)
return result
我们通过分析发现上面算法的时间复杂度是o(n^2)
级别的(pChar == sChar
要花费O(n)
的时间),我们有没有O(n)
级别的算法呢?
参照这篇Leetcode 3:无重复字符的最长子串文章最后提到的思路,我们可以很快速的解决这个问题。
我们通过一个变量count
记录p
元素的个数
c b a e b a b a c d
l r
窗口在移动的过程中,如果窗口中的元素在p
中的话,那么我们记录变量count--
(表示一个在pChar
中的元素移入窗口)。
c b a e b a b a c d
l r
如果移动的过程中窗口右边超过了p_len
,我们就要判断窗口左边的元素是不是在p
中,如果在的话,那么count++
(表示一个在pChar
中的元素移除窗口)。
c b a e b a b a c d
l r
如果count
变成了0
,表示窗口中的元素,pChar
中都有。
class Solution:
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
from collections import Counter
s_len, p_len = len(s), len(p)
count = p_len
pChar = Counter(p)
result = []
for i in range(s_len):
if pChar[s[i]] >= 1:
count -= 1
pChar[s[i]] -= 1
if i >= p_len:
if pChar[s[i - p_len]] >= 0:
count += 1
pChar[s[i - p_len]] += 1
if count == 0:
result.append(i - p_len + 1)
return result
对于这个算法有几点要注意,首先count
表示p
中的元素个数,如果s
中的非p
内元素,我们不用对count
操作。另外,不论s
中的元素是否在p
中,我们都要对pChar
操作(我们在pChar
初始化的时候给与p
中元素计数,这样就区分开来了p
中元素和非p
中元素)。
现在这个算法的时间复杂度就是O(n)
级别的了。
该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!