0438-找到字符串中所有字母异位词

Raphael Liu Lv10

给定两个字符串 sp,找到 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 * 104
  • sp 仅包含小写字母

方法一:滑动窗口

思路

根据题目要求,我们需要在字符串 $s$ 寻找字符串 $p$ 的异位词。因为字符串 $p$ 的异位词的长度一定与字符串 $p$ 的长度相同,所以我们可以在字符串 $s$ 中构造一个长度为与字符串 $p$ 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 $p$ 中每种字母的数量相同时,则说明当前窗口为字符串 $p$ 的异位词。

算法

在算法的实现中,我们可以使用数组来存储字符串 $p$ 和滑动窗口中每种字母的数量。

细节

当字符串 $s$ 的长度小于字符串 $p$ 的长度时,字符串 $s$ 中一定不存在字符串 $p$ 的异位词。但是因为字符串 $s$ 中无法构造长度与字符串 $p$ 的长度相同的窗口,所以这种情况需要单独处理。

代码

[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
s_len, p_len = len(s), len(p)

if s_len < p_len:
return []

ans = []
s_count = [0] * 26
p_count = [0] * 26
for i in range(p_len):
s_count[ord(s[i]) - 97] += 1
p_count[ord(p[i]) - 97] += 1

if s_count == p_count:
ans.append(0)

for i in range(s_len - p_len):
s_count[ord(s[i]) - 97] -= 1
s_count[ord(s[i + p_len]) - 97] += 1

if s_count == p_count:
ans.append(i + 1)

return ans
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();

if (sLen < pLen) {
return new ArrayList<Integer>();
}

List<Integer> ans = new ArrayList<Integer>();
int[] sCount = new int[26];
int[] pCount = new int[26];
for (int i = 0; i < pLen; ++i) {
++sCount[s.charAt(i) - 'a'];
++pCount[p.charAt(i) - 'a'];
}

if (Arrays.equals(sCount, pCount)) {
ans.add(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
--sCount[s.charAt(i) - 'a'];
++sCount[s.charAt(i + pLen) - 'a'];

if (Arrays.equals(sCount, pCount)) {
ans.add(i + 1);
}
}

return ans;
}
}
[sol1-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Solution {
public IList<int> FindAnagrams(string s, string p) {
int sLen = s.Length, pLen = p.Length;

if (sLen < pLen) {
return new List<int>();
}

IList<int> ans = new List<int>();
int[] sCount = new int[26];
int[] pCount = new int[26];
for (int i = 0; i < pLen; ++i) {
++sCount[s[i] - 'a'];
++pCount[p[i] - 'a'];
}

if (Enumerable.SequenceEqual(sCount, pCount)) {
ans.Add(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
--sCount[s[i] - 'a'];
++sCount[s[i + pLen] - 'a'];

if (Enumerable.SequenceEqual(sCount, pCount)) {
ans.Add(i + 1);
}
}

return ans;
}
}
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int sLen = s.size(), pLen = p.size();

if (sLen < pLen) {
return vector<int>();
}

vector<int> ans;
vector<int> sCount(26);
vector<int> pCount(26);
for (int i = 0; i < pLen; ++i) {
++sCount[s[i] - 'a'];
++pCount[p[i] - 'a'];
}

if (sCount == pCount) {
ans.emplace_back(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
--sCount[s[i] - 'a'];
++sCount[s[i + pLen] - 'a'];

if (sCount == pCount) {
ans.emplace_back(i + 1);
}
}

return ans;
}
};
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func findAnagrams(s, p string) (ans []int) {
sLen, pLen := len(s), len(p)
if sLen < pLen {
return
}

var sCount, pCount [26]int
for i, ch := range p {
sCount[s[i]-'a']++
pCount[ch-'a']++
}
if sCount == pCount {
ans = append(ans, 0)
}

for i, ch := range s[:sLen-pLen] {
sCount[ch-'a']--
sCount[s[i+pLen]-'a']++
if sCount == pCount {
ans = append(ans, i+1)
}
}
return
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var findAnagrams = function(s, p) {
const sLen = s.length, pLen = p.length;

if (sLen < pLen) {
return [];
}

const ans = [];
const sCount = new Array(26).fill(0);
const pCount = new Array(26).fill(0);
for (let i = 0; i < pLen; ++i) {
++sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
++pCount[p[i].charCodeAt() - 'a'.charCodeAt()];
}

if (sCount.toString() === pCount.toString()) {
ans.push(0);
}

for (let i = 0; i < sLen - pLen; ++i) {
--sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
++sCount[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];

if (sCount.toString() === pCount.toString()) {
ans.push(i + 1);
}
}

return ans;
};

复杂度分析

  • 时间复杂度:$O(m + (n-m) \times \Sigma)$,其中 $n$ 为字符串 $s$ 的长度,$m$ 为字符串 $p$ 的长度,$\Sigma$ 为所有可能的字符数。我们需要 $O(m)$ 来统计字符串 $p$ 中每种字母的数量;需要 $O(m)$ 来初始化滑动窗口;需要判断 $n-m+1$ 个滑动窗口中每种字母的数量是否与字符串 $p$ 中每种字母的数量相同,每次判断需要 $O(\Sigma)$ 。因为 $s$ 和 $p$ 仅包含小写字母,所以 $\Sigma = 26$。

  • 空间复杂度:$O(\Sigma)$。用于存储字符串 $p$ 和滑动窗口中每种字母的数量。

方法二:优化的滑动窗口

思路和算法

在方法一的基础上,我们不再分别统计滑动窗口和字符串 $p$ 中每种字母的数量,而是统计滑动窗口和字符串 $p$ 中每种字母数量的差;并引入变量 differ 来记录当前窗口与字符串 $p$ 中数量不同的字母的个数,并在滑动窗口的过程中维护它。

在判断滑动窗口中每种字母的数量与字符串 $p$ 中每种字母的数量是否相同时,只需要判断 differ 是否为零即可。

代码

[sol2-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
s_len, p_len = len(s), len(p)

if s_len < p_len:
return []

ans = []
count = [0] * 26
for i in range(p_len):
count[ord(s[i]) - 97] += 1
count[ord(p[i]) - 97] -= 1

differ = [c != 0 for c in count].count(True)

if differ == 0:
ans.append(0)

for i in range(s_len - p_len):
if count[ord(s[i]) - 97] == 1: # 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
differ -= 1
elif count[ord(s[i]) - 97] == 0: # 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
differ += 1
count[ord(s[i]) - 97] -= 1

if count[ord(s[i + p_len]) - 97] == -1: # 窗口中字母 s[i+p_len] 的数量与字符串 p 中的数量从不同变得相同
differ -= 1
elif count[ord(s[i + p_len]) - 97] == 0: # 窗口中字母 s[i+p_len] 的数量与字符串 p 中的数量从相同变得不同
differ += 1
count[ord(s[i + p_len]) - 97] += 1

if differ == 0:
ans.append(i + 1)

return ans
[sol2-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();

if (sLen < pLen) {
return new ArrayList<Integer>();
}

List<Integer> ans = new ArrayList<Integer>();
int[] count = new int[26];
for (int i = 0; i < pLen; ++i) {
++count[s.charAt(i) - 'a'];
--count[p.charAt(i) - 'a'];
}

int differ = 0;
for (int j = 0; j < 26; ++j) {
if (count[j] != 0) {
++differ;
}
}

if (differ == 0) {
ans.add(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
if (count[s.charAt(i) - 'a'] == 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s.charAt(i) - 'a'] == 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
--count[s.charAt(i) - 'a'];

if (count[s.charAt(i + pLen) - 'a'] == -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s.charAt(i + pLen) - 'a'] == 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
++count[s.charAt(i + pLen) - 'a'];

if (differ == 0) {
ans.add(i + 1);
}
}

return ans;
}
}
[sol2-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Solution {
public IList<int> FindAnagrams(string s, string p) {
int sLen = s.Length, pLen = p.Length;

if (sLen < pLen) {
return new List<int>();
}

IList<int> ans = new List<int>();
int[] count = new int[26];
for (int i = 0; i < pLen; ++i) {
++count[s[i] - 'a'];
--count[p[i] - 'a'];
}

int differ = 0;
for (int j = 0; j < 26; ++j) {
if (count[j] != 0) {
++differ;
}
}

if (differ == 0) {
ans.Add(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
if (count[s[i] - 'a'] == 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i] - 'a'] == 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
--count[s[i] - 'a'];

if (count[s[i + pLen] - 'a'] == -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i + pLen] - 'a'] == 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
++count[s[i + pLen] - 'a'];

if (differ == 0) {
ans.Add(i + 1);
}
}

return ans;
}
}
[sol2-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int sLen = s.size(), pLen = p.size();

if (sLen < pLen) {
return vector<int>();
}

vector<int> ans;
vector<int> count(26);
for (int i = 0; i < pLen; ++i) {
++count[s[i] - 'a'];
--count[p[i] - 'a'];
}

int differ = 0;
for (int j = 0; j < 26; ++j) {
if (count[j] != 0) {
++differ;
}
}

if (differ == 0) {
ans.emplace_back(0);
}

for (int i = 0; i < sLen - pLen; ++i) {
if (count[s[i] - 'a'] == 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i] - 'a'] == 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
--count[s[i] - 'a'];

if (count[s[i + pLen] - 'a'] == -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i + pLen] - 'a'] == 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
++count[s[i + pLen] - 'a'];

if (differ == 0) {
ans.emplace_back(i + 1);
}
}

return ans;
}
};
[sol2-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
func findAnagrams(s, p string) (ans []int) {
sLen, pLen := len(s), len(p)
if sLen < pLen {
return
}

count := [26]int{}
for i, ch := range p {
count[s[i]-'a']++
count[ch-'a']--
}

differ := 0
for _, c := range count {
if c != 0 {
differ++
}
}
if differ == 0 {
ans = append(ans, 0)
}

for i, ch := range s[:sLen-pLen] {
if count[ch-'a'] == 1 { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
differ--
} else if count[ch-'a'] == 0 { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
differ++
}
count[ch-'a']--

if count[s[i+pLen]-'a'] == -1 { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
differ--
} else if count[s[i+pLen]-'a'] == 0 { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
differ++
}
count[s[i+pLen]-'a']++

if differ == 0 {
ans = append(ans, i+1)
}
}
return
}
[sol2-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var findAnagrams = function(s, p) {
const sLen = s.length, pLen = p.length;

if (sLen < pLen) {
return [];
}

const ans = [];
const count = Array(26).fill(0);
for (let i = 0; i < pLen; ++i) {
++count[s[i].charCodeAt() - 'a'.charCodeAt()];
--count[p[i].charCodeAt() - 'a'.charCodeAt()];
}

let differ = 0;
for (let j = 0; j < 26; ++j) {
if (count[j] !== 0) {
++differ;
}
}

if (differ === 0) {
ans.push(0);
}

for (let i = 0; i < sLen - pLen; ++i) {
if (count[s[i].charCodeAt() - 'a'.charCodeAt()] === 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i].charCodeAt() - 'a'.charCodeAt()] === 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
--count[s[i].charCodeAt() - 'a'.charCodeAt()];

if (count[s[i + pLen].charCodeAt() - 'a'.charCodeAt()] === -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s[i + pLen].charCodeAt() - 'a'.charCodeAt()] === 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
++count[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];

if (differ === 0) {
ans.push(i + 1);
}
}

return ans;
};

复杂度分析

  • 时间复杂度:$O(n+m+\Sigma)$,其中 $n$ 为字符串 $s$ 的长度,$m$ 为字符串 $p$ 的长度,其中$\Sigma$ 为所有可能的字符数。我们需要 $O(m)$ 来统计字符串 $p$ 中每种字母的数量;需要 $O(m)$ 来初始化滑动窗口;需要 $O(\Sigma)$ 来初始化 differ;需要 $O(n-m)$ 来滑动窗口并判断窗口内每种字母的数量是否与字符串 $p$ 中每种字母的数量相同,每次判断需要 $O(1)$ 。因为 $s$ 和 $p$ 仅包含小写字母,所以 $\Sigma = 26$。

  • 空间复杂度:$O(\Sigma)$。用于存储滑动窗口和字符串 $p$ 中每种字母数量的差。

 Comments
On this page
0438-找到字符串中所有字母异位词