题目
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
题解
定长滑动窗口
记录字符数量
算法
对长度为pLen
的字符串p
,维护一个长度为pLen
的窗口,在vector<int>
pCount
中记录窗口中字符数量
然后对字符串s
的前pLen
位,同样维护一个长度为pLen
的窗口,在sCount
中记录窗口中字符数量
比较pCount
和sCount
,若相等,则记录位置0。
然后开始滑动s
上的窗口,在sCount
中减去出去字符的数量,加上进来字符的数量
在每个位置pCount
和sCount
,并记录相等时的位置。
代码
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;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/1123971/zhao-dao-zi-fu-chuan-zhong-suo-you-zi-mu-xzin/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度
- 时间复杂度:O(m+(n−m)×Σ),其中 n 为字符串 s 的长度,m 为字符串p 的长度,Σ 为所有可能的字符数。因为 s 和 p 仅包含小写字母,所以 Σ=26。
- 空间复杂度:O(Σ)。用于存储字符串 p 和滑动窗口中每种字母的数量。
记录字符差
算法
维护一个大小为26的vector<int>
count
来记录s
和p
中的26种字符数量之差,维护一个diff表示s
和p
中 字符数不同 的 字符 的种类。
统计s
中字符数量,减去p中字符数量,得到count
,其中大于0的元素表示窗口中s
中对应字符比p
多的数量,小于0的元素表示窗口中s
中对应字符比p
少的数量
在每个位置,对于即将出窗口的字符c
,若count
中c
对应值为1,说明,窗口中s
中对应字符只比p
多1,当c
出窗口,s
和p
中c
字符数将相等,diff减一。
若count
中c
对应值为0,说明,窗口中s
和p
中字符c
数量相等,当c
出窗口,s
和p
中c
字符数不同的字符的种类将增加,diff加一。
在每个位置,对于即将进窗口的字符c
,若count
中c
对应值为-1
,说明,窗口中s
中对应字符只比p
少1,当c
进窗口,s
和p
中c
字符数将相等,diff减一。
若count
中c
对应值为0
,说明,窗口中s
和p
中字符c
数量相等,当c
出窗口,s
和p
中c
字符数不同的字符的种类将增加,diff加一。
代码
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;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/1123971/zhao-dao-zi-fu-chuan-zhong-suo-you-zi-mu-xzin/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度
- 时间复杂度:O(n+m+Σ),其中 n 为字符串 s 的长度,m 为字符串p 的长度,Σ 为所有可能的字符数。因为 s 和 p 仅包含小写字母,所以 Σ=26。
- 空间复杂度:O(Σ)。用于存储字符串 p 和滑动窗口中每种字母的数量。
变长滑动窗口
算法
维护一个count
来记录p
中各字符与s
窗口中字符之差,
初始窗口长度为0,因此各count
各元素值等于p
中各字符数量。
每次循环,窗口右边界向右滑动 1 单位。
当窗口右侧进入新字符c
,count
中对应元素值减 1。若count的对应元素值count[c]
<0,说明窗口中字符c
数量比p
中字符c
数量多,此时,窗口左边界向右收缩,出去的字符在count
中对应元素值加 1,收缩直到count
中c
对应值count[c]
=0,也就是最后窗口中出去了一个字符c
。
此时,计算窗口长度,若等于p
长度,说明窗口中字符与p
中字符相同,记录位置。
分析可得,窗口大小一定小于等于字符串p
的大小。对于每种字符c
,窗口中该字符c
的数量一定小于等于p
,当且仅当窗口长度等于p
长度时,窗口中字符与p
中字符相同。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int n = s.size(), m = p.size();
if(n < m) return {};
vector<int> count(26);
for(auto ch : p) ++count[ch - 'a'];
vector<int> ret;
for(int l = 0, r = 0; r < n; ++r) {
--count[s[r] - 'a'];
while(count[s[r] - 'a'] < 0) {
++count[s[l] - 'a'];
++l;
}
if(r - l + 1 == m) ret.push_back(l);
}
return ret;
}
};
//https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/1123971/zhao-dao-zi-fu-chuan-zhong-suo-you-zi-mu-xzin/comments/1291207
复杂度
- 时间复杂度:O(n+m),其中 n 为字符串 s 的长度,m 为字符串p 的长度。看似是for循环中套了一个while循环,实际指针
l
和指针r
只向右移动,最大移动距离为n
。 - 空间复杂度:O(Σ)。Σ 为所有可能的字符数。因为 s 和 p 仅包含小写字母,所以 Σ=26。