专题二_滑动窗口_最小覆盖子串
一:题目
解释:从s中找一个最短子串,该子串能够覆盖t字符串!
例子:AABBCC 可以覆盖ABBC,子串每种类型的字符数量可以多,但是不能少!
所以此题,和专题二_滑动窗口_找到字符串中所有字母异位词-CSDN博客类似!仍采用的是用conut来记录有效字符!所以先看上一道题,才能理解这道题目!
二:算法
①:暴力
left以不同元素作为起点,right从left处开始向右遍历截取字符串,第一个符合要求的字符串,就记录该字符串的起点和长度,然后left++,right又从left处开始向右遍历,以此类推!
所以暴力解法的时间复杂度为O(N^2)
至于怎么判断字符串符合要求,则对比该字符串和t字符串,用哈希表即可!
②:滑动窗口
暴力能优化的点:
当截取到符合要求的字符串之后,我们的left不再仅++,right也不回到left处向后开始遍历!而是让left一直++,走到某个字符之后,使得字符串中的某个字符个数,一定会小于t中该字符的个数,此时字符串一定是不符合题目要求的!
解释如下:
例子1:输入:s = "ADOBECODEBANC", t = "ABC"
第一次截取到的符合要求字符串为"ADOBEC",然后left直接移动到A的后一个,使得字符串成为"DOBEC",此时字符串中的A是小于t中的A的个数,所以count就会--了
例子2:输入:s = "AAOBECODEBANC", t = "ABC"
第一次截取到的符合要求字符串为"AADOBEC",然后left直接移动到A的后一个,使得字符串成为"ADOBEC",此时字符串中的A是小于t中的A的个数,所以count就会--了
后面个例子更清晰说明,只要截取的字符串中的A的频次未达到t中的A的频次,则无效!
所以此时双指针同向移动,滑动窗口即可解决!时间复杂度为O(N)
1:不过此题较难的点在与,之前的滑动窗口都是当窗口不符合要求的时候,我们才会出窗口,但是此题是符合要求的时候,我们才会出窗口!
2:因为比如现在截取的字符串是"ADOBEC",其能覆盖t = "ABC",则我们应该马上对字符串的长度判断是否更短去更新,然后再不断的舍弃字符串中left处的元素(出窗口),直到从left到right的字符串不再符合题目要求,此时我们才让其继续进窗口,去找下一次符合题目要求的字符串,再对新的字符串进行判断更新!
3:并且将符合要求字符串通过出窗口变为不符合要求的字符串这个过程中,字符串一定是符合要求的例子: t = "ABC"
AAAABBC(符合)--->AAABBC(符合)--->AABBC(符合)--->ABBC(符合)--->BBC(不符合要求)
所以我们在出窗口期间,需要一直去判断更新结果!
4:当然,哈希表也可以用数组来模拟,因为元素是单个的字符,可以直接作为数组的下标!
三:代码
①:哈希表
class Solution {
public:string minWindow(string s, string t) {//两个哈希表的定义unordered_map<char,int> hash1;//存储窗口的哈希表unordered_map<char,int> hash2;//存储t的哈希表int num = 0;//t中字符的类型数目int begin=-1,minlen=INT_MAX;//保留最优字符串的 起点和长度 方便使用substr从s中截取字符串返回for(auto c:t){if(hash2[c]++ == 0) num++;//统计t的类型数目 所有v值为0的时候 才让num++}for(int left = 0,right = 0,count = 0;right<s.size();right++){//进窗口char in = s[right];hash1[in]++;if(hash1[in] == hash2[in]) count++;//v值相等 才记作有效 conut++//区间符合要求 则去找下一个符合要求的区间while(count == num){ //先判断更新结果 因为当前字符串可能更短if(right-left+1 < minlen){minlen = right-left+1;begin = left;}//出窗口char out = s[left];hash1[out]--;if(hash1[out]<hash2[out]) count--;//v值只要小于 就记作无效 count--left++;}}if(begin == -1) return "";//对无答案的题目 返回空串return s.substr(begin,minlen);}
};
重点:
1:
for(auto c:t){if(hash2[c]++ == 0) num++;//统计t的类型数目 所有v值为0的时候 才让num++}
num统计的是t字符串中的字符类型,而不是字符个数,所以只有当入哈希表时v值为0,num++
2:
if(hash1[in] == hash2[in]) count++;//v值相等 才记作有效 conut++
进窗口后,使得两个哈希表的频率相同,才记作有效!count++!
3:
if(hash1[out]<hash2[out]) count--;//v值只要小于 就记作无效 count--
同理,当频次不同的时候,直接记作无效!count--!
4:
正确写法!!!!!//区间符合要求 则去找下一个符合要求的区间while(count == num){ //先判断更新结果 因为当前字符串可能更短if(right-left+1 < minlen){minlen = right-left+1;begin = left;}//出窗口char out = s[left];hash1[out]--;if(hash1[out]<hash2[out]) count--;//v值只要小于 就记作无效 count--left++;}------------------------------------------------------------------------------------------
错误写法!!!!!//区间符合要求 则去找下一个符合要求的区间while(count == num){ //出窗口char out = s[left];hash1[out]--;if(hash1[out]<hash2[out]) count--;//v值只要小于 就记作无效 count--left++;//先判断更新结果 因为当前字符串可能更短if(right-left+1 < minlen){minlen = right-left+1;begin = left;}}
正如上文所言,出窗口的过程中,会随循环一直判断更新。并且!判断更新一定是放在出窗口的前面的,否则会出错,因为你出了窗口后,可能会变成不符合要求的字符串,此时你去判断这个不符合要求的字符串去更新结果,有可能此不符合要求的字符串是目前最短的字符串!
②:数组
哈希表的插入删除比较都会耗费时间,而数组模拟哈希表会更快!
class Solution {
public:string minWindow(string s, string t) {//两个哈希表的定义int hash1[128]={0};//存储窗口的哈希表int hash2[128]={0};//存储t的哈希表int num = 0;//t中字符的类型数目int begin=-1,minlen=INT_MAX;//保留最优字符串的 起点和长度 方便使用substr从s中截取字符串返回for(auto c:t){if(hash2[c]++ == 0) num++;//统计t的类型数目 所有v值为0的时候 才让num++}for(int left = 0,right = 0,count = 0;right<s.size();right++){//进窗口char in = s[right];hash1[in]++;if(hash1[in] == hash2[in]) count++;//v值相等 才记作有效 conut++//区间符合要求 则去找下一个符合要求的区间while(count == num){ //先判断更新结果 因为当前字符串可能更短if(right-left+1 < minlen){minlen = right-left+1;begin = left;}//出窗口char out = s[left];hash1[out]--;if(hash1[out]<hash2[out]) count--;//v值只要小于 就记作无效 count--left++;}}if(begin == -1) return "";//对无答案的题目 返回空串return s.substr(begin,minlen);}
};
解释:只用修改定义哈希表的时候,即可