滑动窗口(双指针)
要在字符串S里找出包含字符串T的所有字符的最小子串(注意只需要包含字符串T的所有字符),不需要子串就是T。
所以我们可以扫描一遍字符串S,找出一个满足条件包含字符串T的所有字符串子串,然后根据长度是否比之前记录的字符串长度小,更新最小子串。
扫描字符串S需要用到滑动窗口(双指针),指针j表示子串的起始位置,子串i表示字符串的结束位置。
由于需要计算字符的出现次数,所以考虑用哈希表。
开两个<char, int>型哈希表hashT和hashWindow,hashT表示字符串中各个字符的出现次数,
hashWindow表示当前子串中各个字符的出现次数。
有了哈希表,还需要判断子串中是否包含了字符串T中的所有字符呢,这里我们引入一个变量cnt表示有效数字,
有效数字的计算规则是:遍历子串,如果字符是字符串T中的字符且出现次数没有超过字符串T中该字符出现的次数,则cnt加一。
因为子串中可能包含许多冗余字符。比如某字符s[i]虽然是T中的字符,
但是在子串中的出现次数已经超过了T中该字符出现的次数(hashWindow[s[i]] > hashT[s[i]]),这时cnt是不会加一的。
只有字符是T中字符,且在子串中出现次数小于T中出现次数时,有效数字cnt才会加一。
有了cnt之后,只要cnt和字符串T的大小相等,就可以认为当前子串包含了字符串T中的所有字符,这时再更新一下满足题意的最小子串即可。
代码如下:
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> hashT, hashWindow; //两个哈希表分别表示字符串t和当前子串中各个字符出现的次数
for(auto c : t) { //计算t中各字符出现次数
++hashT[c];
}
string res;
int cnt = 0;
for(int i = 0, j = 0; i < s.size(); ++i) { //字符串s的j ~ i部分为滑动窗口,表示当前子串
++hashWindow[s[i]];
if(hashWindow[s[i]] <= hashT[s[i]]) { //新增加的字符s[i]是t中字符且出现次数小于等于t中该字符出现次数,有效数字加一
++cnt;
}
while(hashWindow[s[j]] > hashT[s[j]]) { //由于为了新增加一个有效字符s[i],中间可能加入了很多冗余字符,因此需要判断子串的左边部分是否可以删减
--hashWindow[s[j]]; //删减的条件就是,子串左边的字符出现次数已经超过了字符串t中该字符出现的次数,这时左指针可以右移,问题不大
++j;
}
if(cnt == t.size()) { //有效数字等于字符串t的大小,表示当前子串包含t中的所有字符
if(res.empty() || res.size() > i - j + 1) { //如果res为空(表示现在这个子串是第一个满足条件的子串)或者当前子串长度小于之前记录的子串
res = s.substr(j, i - j + 1); //更新最小子串
}
}
}
return res;
}
};