跳转至

剑指 Offer II 015. 字符串中的所有变位词

题目描述

给定两个字符串 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 * 104
  • s 和 p 仅包含小写字母

 

注意:本题与主站 438 题相同: https://leetcode.cn/problems/find-all-anagrams-in-a-string/

解法

方法一:滑动窗口

不妨记字符串 $s$ 的长度为 $m$,字符串 $p$ 的长度为 $n$。

我们观察发现,题目实际上等价于判断字符串 $s$ 中是否存在窗口大小为 $n$,且窗口内的字符及其个数与字符串 $p$ 相同的子串。

因此,我们先用哈希表或数组 $cnt2$ 统计字符串 $p$ 中每个字符出现的次数,然后遍历字符串 $s$,维护一个窗口大小为 $n$ 的滑动窗口,用哈希表或数组 $cnt1$ 统计窗口内每个字符出现的次数,当 $cnt1 = cnt2$ 时,说明窗口内的字符及其个数与字符串 $p$ 相同,我们将窗口的起始索引加入答案数组。

遍历结束后,返回答案数组。

时间复杂度 $(m + n \times |\Sigma|)$,空间复杂度 $O(|\Sigma|)$。其中 $m$ 和 $n$ 分别为字符串 $s$ 和 $p$ 的长度;而 $|\Sigma|$ 为字符集的大小,本题中 $|\Sigma|=26$。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        m, n = len(s), len(p)
        if m < n:
            return []
        cnt1 = Counter(s[:n])
        cnt2 = Counter(p)
        ans = []
        if cnt1 == cnt2:
            ans.append(0)
        for i in range(n, m):
            cnt1[s[i]] += 1
            cnt1[s[i - n]] -= 1
            if cnt1 == cnt2:
                ans.append(i - n + 1)
        return ans
 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
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int m = s.length();
        int n = p.length();
        List<Integer> ans = new ArrayList<>();
        if (m < n) {
            return ans;
        }
        int[] cnt1 = new int[26];
        int[] cnt2 = new int[26];
        for (int i = 0; i < n; ++i) {
            ++cnt1[s.charAt(i) - 'a'];
            ++cnt2[p.charAt(i) - 'a'];
        }
        if (Arrays.equals(cnt1, cnt2)) {
            ans.add(0);
        }
        for (int i = n; i < m; ++i) {
            ++cnt1[s.charAt(i) - 'a'];
            --cnt1[s.charAt(i - n) - 'a'];
            if (Arrays.equals(cnt1, cnt2)) {
                ans.add(i - n + 1);
            }
        }
        return ans;
    }
}
 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
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int m = s.size();
        int n = p.size();
        vector<int> ans;
        if (m < n) {
            return ans;
        }
        vector<int> cnt1(26), cnt2(26);
        for (int i = 0; i < n; ++i) {
            ++cnt1[s[i] - 'a'];
            ++cnt2[p[i] - 'a'];
        }
        if (cnt1 == cnt2) {
            ans.push_back(0);
        }
        for (int i = n; i < m; ++i) {
            ++cnt1[s[i] - 'a'];
            --cnt1[s[i - n] - 'a'];
            if (cnt1 == cnt2) {
                ans.push_back(i - n + 1);
            }
        }
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func findAnagrams(s string, p string) (ans []int) {
    m, n := len(s), len(p)
    if m < n {
        return
    }
    var cnt1, cnt2 [26]int
    for i, ch := range p {
        cnt1[s[i]-'a']++
        cnt2[ch-'a']++
    }
    if cnt1 == cnt2 {
        ans = append(ans, 0)
    }
    for i := n; i < m; i++ {
        cnt1[s[i]-'a']++
        cnt1[s[i-n]-'a']--
        if cnt1 == cnt2 {
            ans = append(ans, i-n+1)
        }
    }
    return
}
 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
function findAnagrams(s: string, p: string): number[] {
    const m = s.length;
    const n = p.length;
    const ans: number[] = [];
    if (m < n) {
        return ans;
    }
    const cnt1: number[] = new Array(26).fill(0);
    const cnt2: number[] = new Array(26).fill(0);
    for (let i = 0; i < n; ++i) {
        ++cnt1[s[i].charCodeAt(0) - 'a'.charCodeAt(0)];
        ++cnt2[p[i].charCodeAt(0) - 'a'.charCodeAt(0)];
    }
    if (cnt1.toString() === cnt2.toString()) {
        ans.push(0);
    }
    for (let i = n; i < m; ++i) {
        ++cnt1[s[i].charCodeAt(0) - 'a'.charCodeAt(0)];
        --cnt1[s[i - n].charCodeAt(0) - 'a'.charCodeAt(0)];
        if (cnt1.toString() === cnt2.toString()) {
            ans.push(i - n + 1);
        }
    }
    return ans;
}
 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
class Solution {
    func findAnagrams(_ s: String, _ p: String) -> [Int] {
        let m = s.count
        let n = p.count
        var ans = [Int]()

        if m < n {
            return ans
        }

        var cnt1 = Array(repeating: 0, count: 26)
        var cnt2 = Array(repeating: 0, count: 26)

        let aAsciiValue = Character("a").asciiValue!

        for i in 0..<n {
            let sIdx = Int(s.utf8[s.utf8.index(s.utf8.startIndex, offsetBy: i)] - aAsciiValue)
            let pIdx = Int(p.utf8[p.utf8.index(p.utf8.startIndex, offsetBy: i)] - aAsciiValue)
            cnt1[sIdx] += 1
            cnt2[pIdx] += 1
        }

        if cnt1 == cnt2 {
            ans.append(0)
        }

        for i in n..<m {
            let sIdxNew = Int(s.utf8[s.utf8.index(s.utf8.startIndex, offsetBy: i)] - aAsciiValue)
            let sIdxOld = Int(s.utf8[s.utf8.index(s.utf8.startIndex, offsetBy: i - n)] - aAsciiValue)
            cnt1[sIdxNew] += 1
            cnt1[sIdxOld] -= 1

            if cnt1 == cnt2 {
                ans.append(i - n + 1)
            }
        }

        return ans
    }
}

方法二:滑动窗口优化

在方法一中,我们每次加入和移除一个字符时,都需要比较两个哈希表或数组,时间复杂度较高。我们可以维护一个变量 $diff$,表示两个大小为 $n$ 的字符串中,有多少种字符出现的个数不同。当 $diff=0$ 时,说明两个字符串中的字符个数相同。

时间复杂度 $O(m + n + |\Sigma|)$,空间复杂度 $O(|\Sigma|)$。其中 $m$ 和 $n$ 分别为字符串 $s$ 和 $p$ 的长度;而 $|\Sigma|$ 为字符集的大小,本题中 $|\Sigma|=26$。

 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
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        m, n = len(s), len(p)
        if m < n:
            return []
        cnt = Counter()
        for a, b in zip(s, p):
            cnt[a] += 1
            cnt[b] -= 1
        diff = sum(x != 0 for x in cnt.values())
        ans = []
        if diff == 0:
            ans.append(0)
        for i in range(n, m):
            a, b = s[i - n], s[i]
            if cnt[a] == 0:
                diff += 1
            cnt[a] -= 1
            if cnt[a] == 0:
                diff -= 1
            if cnt[b] == 0:
                diff += 1
            cnt[b] += 1
            if cnt[b] == 0:
                diff -= 1
            if diff == 0:
                ans.append(i - n + 1)
        return ans
 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
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int m = s.length();
        int n = p.length();
        List<Integer> ans = new ArrayList<>();
        if (m < n) {
            return ans;
        }
        int[] cnt = new int[26];
        for (int i = 0; i < n; ++i) {
            ++cnt[s.charAt(i) - 'a'];
            --cnt[p.charAt(i) - 'a'];
        }
        int diff = 0;
        for (int x : cnt) {
            if (x != 0) {
                ++diff;
            }
        }
        if (diff == 0) {
            ans.add(0);
        }
        for (int i = n; i < m; ++i) {
            int a = s.charAt(i - n) - 'a';
            int b = s.charAt(i) - 'a';
            if (cnt[a] == 0) {
                ++diff;
            }
            --cnt[a];
            if (cnt[a] == 0) {
                --diff;
            }
            if (cnt[b] == 0) {
                ++diff;
            }
            ++cnt[b];
            if (cnt[b] == 0) {
                --diff;
            }
            if (diff == 0) {
                ans.add(i - n + 1);
            }
        }
        return ans;
    }
}
 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
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int m = s.size(), n = p.size();
        vector<int> ans;
        if (m < n) {
            return ans;
        }
        vector<int> cnt(26);
        for (int i = 0; i < n; ++i) {
            ++cnt[s[i] - 'a'];
            --cnt[p[i] - 'a'];
        }
        int diff = 0;
        for (int x : cnt) {
            if (x != 0) {
                ++diff;
            }
        }
        if (diff == 0) {
            ans.push_back(0);
        }
        for (int i = n; i < m; ++i) {
            int a = s[i - n] - 'a';
            int b = s[i] - 'a';
            if (cnt[a] == 0) {
                ++diff;
            }
            --cnt[a];
            if (cnt[a] == 0) {
                --diff;
            }
            if (cnt[b] == 0) {
                ++diff;
            }
            ++cnt[b];
            if (cnt[b] == 0) {
                --diff;
            }
            if (diff == 0) {
                ans.push_back(i - n + 1);
            }
        }
        return ans;
    }
};
 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
func findAnagrams(s string, p string) (ans []int) {
    m, n := len(s), len(p)
    if m < n {
        return
    }
    cnt := [26]int{}
    for i := 0; i < n; i++ {
        cnt[s[i]-'a']++
        cnt[p[i]-'a']--
    }
    diff := 0
    for _, x := range cnt {
        if x != 0 {
            diff++
        }
    }
    if diff == 0 {
        ans = append(ans, 0)
    }
    for i := n; i < m; i++ {
        a, b := s[i-n]-'a', s[i]-'a'
        if cnt[a] == 0 {
            diff++
        }
        cnt[a]--
        if cnt[a] == 0 {
            diff--
        }
        if cnt[b] == 0 {
            diff++
        }
        cnt[b]++
        if cnt[b] == 0 {
            diff--
        }
        if diff == 0 {
            ans = append(ans, i-n+1)
        }
    }
    return
}
 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
function findAnagrams(s: string, p: string): number[] {
    const m = s.length;
    const n = p.length;
    const ans: number[] = [];
    if (m < n) {
        return ans;
    }
    const cnt: number[] = new Array(26).fill(0);
    for (let i = 0; i < n; ++i) {
        --cnt[p[i].charCodeAt(0) - 'a'.charCodeAt(0)];
        ++cnt[s[i].charCodeAt(0) - 'a'.charCodeAt(0)];
    }
    let diff = 0;
    for (const x of cnt) {
        if (x !== 0) {
            ++diff;
        }
    }
    if (diff === 0) {
        ans.push(0);
    }
    for (let i = n; i < m; ++i) {
        const a = s[i - n].charCodeAt(0) - 'a'.charCodeAt(0);
        const b = s[i].charCodeAt(0) - 'a'.charCodeAt(0);
        if (cnt[a] === 0) {
            ++diff;
        }
        if (--cnt[a] === 0) {
            --diff;
        }
        if (cnt[b] === 0) {
            ++diff;
        }
        if (++cnt[b] === 0) {
            --diff;
        }
        if (diff === 0) {
            ans.push(i - n + 1);
        }
    }
    return ans;
}

评论