简书内代码已上传GitHub:点击我 去GitHub查看代码
这道题和 最小覆盖子串 思路基本一样
题目:
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
![](https://img.haomeiwen.com/i16314622/705e485cb54b414a.png)
![](https://img.haomeiwen.com/i16314622/f37cfec70afe061b.png)
可以看到, 通过率在 35% 左右, 不算太难。
思路:
先回想一下76题是什么,那题让我们求得最小覆盖子串,而这个子串,其实是覆盖了数组s1的序列。再来看看这一题:
-
s2中只要包含s1的排列,那么返回true, 反之false。这其实就是告诉你,你需要找到一个序列,其中包含且仅有s1的所有元素。
-
76题我们找的也是一个序列,但是其中不一定只能有s1中的所有元素
-
这样一看问题就简单了,让我们再对76题做一个简单分析(这很重要,请务必看):
76题
可以看到, 在76题的算法中,我们的窗口大小一开始是没有限制的,直到找到一个大窗口包含了所需序列,再逐渐缩小。而这一题,我们的窗口大小是固定的,窗口大小就是s1数组的大小。
-
所以说这一题就相当于76题的简化版,我们不需要考虑窗口大小问题了,我们的目的仅仅是去找这个固定大小窗口在滑动过程中是否匹配到s1的排列。匹配思想还是和76题一样。
-
如何匹配? 其实记录频率就行,我们关键在于区分一个字符是否是s1中的字符,而区分的方法就是:我们可以用数组存储s1中元素出现的频率。当元素进入窗口,让频率 - 1,如果频率仍然 >= 0,说明这个新入窗的元素是我们需要的元素。同样的出窗操作时频率 + 1。且记录有效元素时进行计数。满足要求时即可返回true。
完整代码:
// 滑动窗口解题
bool checkInclusion(char * s1, char * s2){
// 长度
int len1 = strlen(s1), len2 = strlen(s2);
// 用于计数
int cnt = len1;
// 左右指针
int l, r;
l = 0; r = 0;
// 存储s1字母出现频率
int fre[256] = {0};
//记录频率
for(int i = 0; i < len1; ++i){
++fre[s1[i]];
}
// 遍历开始
while(r < len2){
// 新入窗的元素是s1中的元素,计数-1
if(--fre[s2[r]] >= 0){
--cnt;
}
//滑动窗口
if(r > len1 - 1){
//出窗元素是s1中的元素,计数+1
if(++fre[s2[l++]] > 0){
++cnt;
}
}
//当前窗口符合要求,返回true
if(cnt == 0){
return true;
}
//右移窗口
++r;
}
//执行到这一步,说明不符合,返回false
return false;
}
在写代码的时候要注意几个问题:
-
首先如过有循环语句,不要在循环语句的表达式里出现 strlen() ,因为字符串长度是需要O(n)的时间复杂度求得的这样算法的时间复杂度直接就变成O(n*n)。。。
-
左右指针边界问题,如果让右指针一开始指向窗口右界,那么可能没什么需要注意的。但是如果你让右指针初值为右界 + 1,请注意最后一个窗口的边界问题。。。
-
这题的判断字符是否在字符串中的思想值得好好的归纳。利用fre与0的比较可以将O(n)时间复杂度的判定问题转换为O(1)。
AC
接下来还是继续针对滑动窗口做题,希望能够熟练掌握。
如果有看不懂的,私信我!!!
~^^
每天进步一点,加油!
![](https://img.haomeiwen.com/i16314622/764255f29098cdd7.png)
END
网友评论