给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
思路
- 这道题可以很快得到是滑动窗口的思路,因为是判断每个连续的子串的问题,但是这道题难点在于:如何判断每一个滑动窗口中的字符串是不是覆盖了目标字符串呢?
- 我的第一想法是:用两个哈希表分别存窗口中的字符和目标子串的字符,然后再逐一比较,但是显然是O(mn)的算法,会超时!
- 第二想法(非常巧妙):设置一个correct用于记录滑动窗口中对目标字符串对应字母的有效添加次数,当correct = t.size()的时候便是窗口中的字符串已经覆盖了t,从而省去需要再遍历一次的时间复杂
代码
class Solution {
public:
//滑动窗口收缩到满足题意的最小窗口
string minWindow(string s, string t) {
string result = s + 'a';
int start = 0;
int correct = 0;//记录窗口中有效字母加入的个数,有效字母:在t中出现且个数还不足够
unordered_map<char, int> winMap;
unordered_map<char, int> tMap;
for(char c: t) tMap[c]++;
for(int i = 0; i < s.size(); i++){
winMap[s[i]]++;//先将滑动窗口新来的元素加入
if(winMap[s[i]] <= tMap[s[i]]) correct++;//如果是有效字母
//收缩窗口,收缩到最小满足题意的窗口为止
while(start < i && winMap[s[start]] > tMap[s[start]]){//对于start是冗余字母要删除
winMap[s[start]]--;
start++;
}
//判断是否更新答案,correct不会做减法,因为收缩窗口的时候 不允许收缩到窗口不满足题意
if(correct == t.size() && result.size() > i - start + 1){
result = s.substr(start, i - start + 1);
}
}
if(result.size() == s.size() + 1) return "";
return result;
}
};