这题里字符串的排列的意思是s1中的字符可以交换顺序:
s1 = "ab"
s1的排列包括:"ab", "ba"
只有当两个字符串包含相同次数的相同字符时,一个字符串才是另一个字符串的排列
那就是要在s2中找到一个和s1一样长的子串,使得这个子串和s1是字母异位词(字符全相同,但是排列顺序不同):
字母异位词可以看这个:LeetCode第 242 题:有效的字母异位词(C++)_qq_32523711的博客-CSDN博客,不过这题里面不能用排序这种方法,会超时。
参照这个模板:
leetcode 第 76 题:最小覆盖子串(C++)_qq_32523711的博客-CSDN博客
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for(const auto &c : s1) ++need[c];
int left = 0, right = 0;
int count = 0;
while(right < s2.size()){
auto c = s2[right];
++right;
if(need.count(c) != 0){
++window[c];
//说明字符c在window中已查全,目标是将need中所有字符查全,此时count == need.size()
//代表窗口中的子串是s1的排列
if(need[c] == window[c])
++count;
}
//缩小窗口的时机是窗口大小等于s2.size()时
//此时两者长度相等,如果每个字母出现频率也相等(count == need.size())
//那么说明后者是前者的排列,返回true,否则我们从左边收缩窗口
//这儿的while循环其实只会执行一次(不符合要求就增加左边界,那么循环条件自然不满足了)
//所以可以改为if语句,判断条件改为==就行
//while(right - left >= s1.size()){
if(right - left == s1.size()){ //因为是排列,所以要与s2.size()比较
if(count == need.size()) return true;
auto d = s2[left];
++left;
if(need.count(d)){
//注意window[d]的值是可能大于need[d]的值的
//比如"adc", "dcda"
if(window[d] == need[d])//两者数量刚好相等,移出之后肯定就不等
--count;
--window[d];
}
}
}
return false;
}
};
上述代码用了两个哈希表,其实用一个(need)就可以了,滑动窗口的时候对need中key对应的值减1,直到减为0就说明该key查全了。
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need;
for(const auto &c : s1) ++need[c];
int left = 0, right = 0;
int count = 0;
while(right < s2.size()){
auto c = s2[right];
++right;
if(need.count(c) != 0){
--need[c];
if(need[c] == 0)
++count;
}
//缩小窗口的时机是窗口大小等于s2.size()时
//此时两者长度相等,如果每个字母出现频率也相等(count == need.size())
//那么说明后者是前者的排列,返回true,否则我们从左边收缩窗口
if(right - left == s1.size()){
if(count == need.size()) return true;
auto d = s2[left];
++left;
if(need.count(d)){//存在这个key
if(need[d] == 0)//这个key对应的数量刚好为0
--count;
++need[d];
}
}
}
return false;
}
};
哈希表改为数组也是可以的,因为目的只是计数而已,而且数组的计数以及查找之类操作更快,效率更高(执行用时会优秀很多)。
当然,思路还是一样的
class Solution {
public:
bool checkInclusion(string s1, string s2) {
vector<int> need(26, 0), window(26,0); //小写字母26个
for(const auto &c : s1) ++need[c-'a'];
int target = 0; //记录s1中的不重复字符个数
target = sum(need.begin(), need.end(), );
for(const auto &c : need){
if(c) ++target;
}
int left = 0, right = 0;
int count = 0;
while(right < s2.size()){
auto c = s2[right] - 'a';
++right;
if(need[c] != 0){
++window[c];
//说明字符c在window中已查全,目标是将need中所有字符查全
if(need[c] == window[c])
++count;
}
//缩小窗口的时机是窗口大小(子串长度)right-left == s2.size()时:
//此时如果所有字母均查全(count == target)
//那么说明子串是s1的排列,返回true,否则从左边收缩窗口,去掉一个元素
if(right - left == s1.size()){ //因为是排列,所以子串与s1等长
// 全部查全
if(count == target) return true;
auto d = s2[left] - 'a';
++left;
if(need[d]){
if(window[d] == need[d]) //两者数量刚好相等,移出之后肯定就不等
--count;
--window[d];
}
}
}
return false;
}
};
上面我们用了两个数组,但是其实一个数组就可以(但是这样其实不太必要,一个数组反而会更麻烦):
class Solution {
public:
bool checkInclusion(string s1, string s2) {
vector<int> need(26, 0); //小写字母26个
for(const auto &c : s1) ++need[c-'a'];
int target = 0; //记录s1中不重复字符个数,也即待查全字符个数
for(auto &c : need){
if(c) ++target;
else c = INT_MIN; //把为0的元素全部变为INT_MIN(为了下面考虑)
}
int left = 0, right = 0;
int count = 0;
while(right < s2.size()){
auto c = s2[right] - 'a';
++right;
//说明该字符在s1中,就减少一个对该字符的需求
//但是要注意,need[c]可能会减为负数,比如s1中只需要一个字符z,但是我们的
//窗口中却有两个z(重复字符)的时候,这也是为什么上面会把s1中不存在元素在数组中对应的下标设置为INT_MIN的原因
//当然,其实只要设为一个很小的负数(比如-20)就可以了,目的是为了区别开两类字符(在s1中的和不在s1中的)
if(need[c] > INT_MIN){
--need[c];
//需求为0说明字符c已查全,目标是将need中所有字符查全
if(need[c] == 0)
++count;
}
//缩小窗口的时机是窗口大小(子串长度)right-left == s2.size()时:
//此时如果所有字母均查全(count == target)
//那么说明子串是s1的排列,返回true,否则从左边收缩窗口,去掉一个元素
if(right - left == s1.size()){ //因为是排列,所以子串与s1等长
// 全部查全
if(count == target) return true;
auto d = s2[left] - 'a';
++left;
if(need[d] > INT_MIN){
//如果为0说明供需平衡,把它去掉必然供给不足,那么查全数应该减去1
//其他情况都不能改变count的值
if(need[d] == 0)
--count;
++need[d]; //移出之后需求肯定加1
}
}
}
return false;
}
};