目录
1.题目
https://leetcode.cn/problems/find-all-anagrams-in-a-string/
给定两个字符串
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
仅包含小写字母
2.分析
暴力解法
大致思路:使用i遍历字符串s,从i位置截取和p相同长度的子串(前提p.size()<=s.size(),这个要在一开始就要判断),比较这个子串和p是否是异位词
方法1:排序(超时)
可以先对两个串排序,再调用operator==判断是否相等
class Solution {
public:
vector<int> findAnagrams(string s, string p)
{
if (p.size()>s.size())
return {};
vector<int> ret;
sort(p.begin(),p.end());
for (int i = 0; i <= s.size() - p.size(); i++)
{
string tmp=s.substr(i,p.size());
sort(tmp.begin(),tmp.end());
if (p==tmp)
ret.push_back(i);
}
return ret;
}
};
调用sort函数的时间复杂度是,过高容易超时:
方法2:哈希表(险过)
统计s的子串和p串的词频,记录到哈希表中,再判断两个哈希表是否相同,这个时间复杂度比方法1低一点
★判断两个哈希表是否相同算法(通用方法,必须掌握)
虽然可以用哈希数组hash['z'+1]投机取巧(这个解法在本文的最后),但是其他题可能用不了哈希数组,因此掌握通用的方法是很有必要的
算法:
能相等的前提:两个哈希表的大小相等
if (mp1.size() != mp2.size())
return false;
哈希表有迭代器,可以使用范围for从头到尾遍历
STL的map底层实现是红黑树,对于for (const auto& pair : mp1),pair拿到的是mp1的红黑树中一个节点,到mp2中查有没有相同的节点即可,使用find函数
find函数的查找逻辑:
cplusplus网上是这样说的:https://legacy.cplusplus.com/reference/map/map/find/
find取得(get)指向元素的迭代器(iterator to element)
在容器中查找一个键值与k等价的元素,如果找到,则返回指向该元素的迭代器;否则返回指向map::end的迭代器
比较时会出现两种情况
1.找到一个键值与k等价的元素,此时还不能断定两个节点一定相等,需要比较第二个关键字(pair.second)是否相等,如果不等(it->second != pair.second),返回false
2.没找到一个键值与k等价的元素(it == mp2.end()),返回false
class Solution {
public:
bool check(map<int,int>& mp1,map<int,int>& mp2)
{
if (mp1.size() != mp2.size())
return false;
for (const auto& pair : mp1)
{
auto it = mp2.find(pair.first);
if (it == mp2.end() || it->second != pair.second)
return false;
}
return true;
}
vector<int> findAnagrams(string s, string p)
{
if (p.size()>s.size())
return {};
vector<int> ret;
map<int,int> mp1;
for (int i=0;i<p.size();i++)
mp1[p[i]]++;
for (int i = 0; i <= s.size() - p.size(); i++)
{
map<int,int> mp2;
string tmp=s.substr(i,p.size());
for (int i=0;i<tmp.size();i++)
mp2[tmp[i]]++;
if (check(mp1,mp2))
ret.push_back(i);
}
return ret;
}
};
提交结果
优化方法:定长滑动窗口
以s = "cbaebabacd", p = "abc"为例分析:
异位词子串首先要长度和p从串相等,对s从头到尾遍历即可,
在移动窗口时,注意左侧元素离开哈希表,右侧元素加入哈希表,当mp2[s[left]]==0时,必须删除这个节点,否则影响哈希表的结构
class Solution {
public:
bool check(map<int,int>& mp1,map<int,int>& mp2)
{
if (mp1.size() != mp2.size())
return false;
for (const auto& pair : mp1)
{
auto it = mp2.find(pair.first);
if (it == mp2.end() || it->second != pair.second)
return false;
}
return true;
}
vector<int> findAnagrams(string s, string p)
{
if (p.size()>s.size())
return {};
vector<int> ret;
map<int,int> mp1,mp2;
for (int i=0;i<p.size();i++)
{
mp1[p[i]]++;
mp2[s[i]]++;
}
for (int left=0,right=p.size()-1; right<s.size();left++,right++)
{
if (check(mp1,mp2))
{
ret.push_back(left);
}
mp2[s[left]]--;
mp2[s[right+1]]++;
if (mp2[s[left]]==0)
mp2.erase(s[left]);
}
return ret;
}
};
注:right+1最大为s.size(),此时mp2[s[right+1]]++;访问到字符串的'\0'没有越界,不影响结果
提交结果
使用哈希数组更快
将map<int,int>改成数组,其他地方稍作修改即可
class Solution {
public:
bool check(int* mp1,int* mp2)
{
for(int i='a';i<='z';i++)
if (mp1[i]!=mp2[i])
return false;
return true;
}
vector<int> findAnagrams(string s, string p)
{
if (p.size()>s.size())
return {};
vector<int> ret;
int mp1['z'+1]={0},mp2['z'+1]={0};
for (int i=0;i<p.size();i++)
{
mp1[p[i]]++;
mp2[s[i]]++;
}
for (int left=0,right=p.size()-1; right<s.size();left++,right++)
{
if (check(mp1,mp2))
ret.push_back(left);
mp2[s[left]]--;
mp2[s[right+1]]++;
}
return ret;
}
};
提交结果
★★★更优化的方法:不定长滑动窗口(比定长的要快!)
hash_s是字符串s的滑动窗口的哈希数组,hash_p是字符串p的的哈希数组
上面的优化的方法还可以继续优化,引入有效字符的个数:
先让hash_s[s[right]]++,之后判断:
1.当hash_s[s[right]] <= hash_p[s[right]]时计入有效字符的个数count,即count++
2.一旦窗口长度大于len时,及时调整让left++,当hash_s[s[left]] <= hash_p[s[left]]时减小有效字符的个数count,即count--
★更新结果的条件:窗口长度相等,且有效字符的个数要相等,这样就不用像上面方法那样遍历mp1和mp2数组的每个元素
个数从0变成1,有效字符的个数+1,因此count++
个数从1变成0,有效字符的个数-1,因此count--
符合有效字符的个数==p串的长度,将left尾插到返回数组ret
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
if (p.size() > s.size())
return {};
vector<int> ret;
int len = p.size();
int count = 0;
int hash_s['z'+1] = {0}, hash_p['z'+1] = {0};
for (int i = 0; i < p.size(); i++)
hash_p[p[i]]++;
for (int left = 0, right = 0; right < s.size(); right++)
{
hash_s[s[right]]++;
if (hash_s[s[right]] <= hash_p[s[right]])
count++;
if (right - left + 1 > len)
{
if (hash_s[s[left]] <= hash_p[s[left]])
count--;
hash_s[s[left]]--;
left++;
}
if (count == len)
ret.push_back(left);
}
return ret;
}
};