438. 找到字符串中所有字母异位词

发布时间 2023-12-16 20:55:13作者: DawnTraveler

1.题目介绍

给定两个字符串 \(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 * 10^{4}\)
  • \(s\) 和 \(p\) 仅包含小写字母

2.题解

2.1 滑动窗口

思路

这里便是使用滑动窗口的一种典型情形,由于窗口大小固定为p.length();
所以可以直接使用数组来存储每个字母出现次数,并通过窗口不断向后移动进行一次次判断(这里==是向量vector重载后的符号,比较每一个元素)
注意这里最后一个窗口起始值是s.length()-p.length(),而不是s.length()-p.length()-1
因为s.length() = 下标A + 1, 下标B = 下标A -(回退p.length()-1 个单位)[自身+回退的p.length()-1 = p.length()];
即下标B = 下标A - p.length() + 1 = s.length() - 1 + p.length() +1 = s.length() - p.length();

代码

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        if (s.length() < p.length()) return vector<int>();
        vector<int> ans;
        vector<int> sCount(26);
        vector<int> pCount(26);
        for (int i = 0; i < p.length(); i++){
            sCount[s[i]-'a']++;
            pCount[p[i]-'a']++;
        }
        if (sCount == pCount)  ans.push_back(0);

        for (int i = 1; i <= s.length()-p.length(); i++){
            --sCount[s[i-1] - 'a'];
            ++sCount[s[p.length()+i-1] - 'a'];
            if (sCount == pCount) ans.push_back(i);
        }
        return ans;
    }
};

复杂度分析

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

2.2滑动窗口优化

思路

这里直接使用一个计数器 differ 来统计不同的字符数目,而不需要再遍历整个数组进行比较。

代码

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        if (s.length() < p.length()) return vector<int>();
        vector<int> ans;
        vector<int> count(26);
        for (int i = 0; i < p.length(); i++){
            count[s[i]-'a']++;  //滑动窗口中多出的字母元素个数
            count[p[i]-'a']--;  //滑动窗口中少出的字母元素个数
        }
        int differ = 0;
        for (int i = 0; i < 26; i++)
            if(count[i] != 0) differ++;
        if (differ == 0) ans.push_back(0);

        for (int i = 0; i < s.length()-p.length(); i++){
            // 首先是滑动窗口滑动排出的首个元素
            if (count[s[i]-'a'] == 1) differ--; //排出的正好是多的那个,differ--
            if (count[s[i]-'a'] == 0) differ++; //本来刚刚好,现在少了,differ++
            count[s[i] -'a']--;
            // 然后是滑动窗口滑进的新元素
            if (count[s[i+p.length()]-'a'] == -1) differ--; //新增的刚好是少一个的哪个,differ--
            if (count[s[i+p.length()]-'a'] == 0) differ++; //本来刚好,现在多一个了
            count[s[i+p.length()]-'a'] ++;
            if (differ == 0) ans.push_back(i+1);
        }
        return ans;
    }
};

复杂度分析

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