标题
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
思路
最朴素的做法:类似于朴素字符串匹配,依次从第一个位置寻找一个符合的字符串,然后在从第二个位置寻找符合的一个字符串。。。从第n位置开始寻找一个符合字符串。寻找出他们其中的最小值,然后输出。(太暴力,超时)
但是我们可以从上述思路中发现,每次寻找的字符串都会建立出一个窗口,此时我们可以尝试一下滑动窗口的思想。
建立一个类似于计算机网络中tcp滑动窗口的概念,当窗口太大了(窗口里的子串包含t中所有元素)我们将窗口左端往右移(减小窗口),当窗口太小了(窗口里的子串不包含t中所有元素)我们将窗口右端右移(扩大窗口)。
注释:我们寻找到是窗口长度的最小值(同时窗口需要包含t中所有元素)
如何知道窗口包含t所有元素:建立一个flag哈希表记录t中所有元素及其出现次数,并与窗口内的元素不断比较(可以用cnt记录窗口内的元素及其出现次数)
代码
class Solution {
public:
unordered_map<char, int> cnt, flag;
string minWindow(string s, string t) {
int len1 = s.size(), len2 = t.size();
if(len1<len2) return "";
int l = 0, r = 0; //窗口左端l,右端r
int minn = 100000; //s中包含t元素的最小子串长度
string ans = ""; //输出结果
for(auto &x:t){
flag[x]++; //将t中所有元素及出现次数记录下来
}
//开始滑动窗口
while(r<len1){ //窗口右端必须在s串内
cnt[s[r]]++; //将右端存入cnt
while(check()&&l<=r){ //左端滑动
if(minn>r-l+1){ //比较子串最小长度
minn = r-l+1;
ans = s.substr(l, minn);
}
cnt[s[l]]--; //将左端元素删除
l++; //左端左移
}
r++; //右端右移
}
return ans;
}
//检查t中的元素是不是都在窗口内
bool check(){
for(auto& [x, y]:flag){
if(y>cnt[x]){
return false;
}
}
return true;
}
};