438. 找到字符串中所有字母异位词
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;
}
};