76. Minimum Window Substring
题目链接
题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "a", t = "a"
输出:"a"
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
思路
用滑动窗口解决,维护一个左指针和一个右指针。
代码及解析
class Solution {
public:
string minWindow(string s, string t) {
map<char, int> needs;
//needs表示当前在s串中需要遍历到的某个字符的数量,
//当needs['a']>0的时候,表示还期待往右遍历到a字符
//当needs['a']=0的时候,表示目前在s串中遇到的a字符的数量已经和t串中a字符的数量相等了
//当needs['a']<0的时候,表示目前在s串中遇到的a字符的数量已经比t串中a字符的数量还要多了
map<char, bool> flag;
for (auto e : t) {
needs[e]++;
flag[e] = true;
}
int l = 0, r = 0, min_l = 0, min_size = INT_MAX, cnt = 0;
//cnt表示当前s串中已经满足t串中字符的数目,
//当cnt==t.size()的时候表示s串中已经满足t串的所有字符了
for (; r < s.size(); r++) {
//满足t串中的一个字符,计数器加一,
//注意,只有needs[s[r]]减完1之后还大于或者等于0,才表示这次在s中遇到的s[r]字符是有效的
//比如t串只有2个a字符,当在s中遇到第3个a字符的时候,我们只需要让--needs['a'],
//此时needs['a'] = -1,表示当前在s中遇到的a字符数目已经多了一个
if (--needs[s[r]] >= 0) {
cnt++;
}
while (cnt == t.size()) {
//当前s串[l, r]之间的字符已经满足t了,但是可能不是最短的,
//意味着这段区间可能要么有些字符是t中不需要的,
//要么是对应的needs小于0,即相应的字符出现次数太多了
//那么为了达到最短的要求,就需要不断把左边界往右移动,即收缩边界
//移动之前首先判断当前[l,r]这个字串的长度是否是小于之前的最小长度min_size
//由于最开始min_size设置为了INT_MAX,所以当第一次走到这里的时候是一定满足条件的
if (r - l + 1 < min_size) {
min_l = l;//更新最小左边界
min_size = r - l + 1;//更新min_size
}
if (flag[s[l]] && ++needs[s[l]] > 0) {
//这段代码比较关键
//如果当前l位置的字符是t中出现过的,那么由于后面左边界会向右移动
//那么会出现两种情况:
//1.假设之前needs[s[l]]刚好等于0,那么当++needs[s[l]]之后就大于0,说明去掉当前字符已经不满足t串中s[l]字符的数量了,也就意味着我们满足t串字符数目要减一。
//2.设之前needs[s[l]]小于0,那么当++needs[s[l]]之后就还是小于或者等于0,这时候不需要更新cnt
//其实这段逻辑就是和上面的代码相对应的,只不过这里还有一种情况,就是当前l处的字符根本不是t中需要的,这种情况,只需要把l++即可。
/*
if(--needs[s[r]] >= 0){
cnt++;
}
*/
cnt--;
}
//注意,我们这里是一定会收缩左边界的,问题来了,假设我们现在[l,r]之间的字符串刚好是我们目标字符串,那么l向右收缩之后,不是不符合条件了吗?那怎么记录这个位置的左边界和有边界呢?
//没错,这个就是通过min_l和min_size记录的,所以我们左边界和右边界都是在往右移动,只不过我们根据条件来更新min_l和min_size。
l++;//收缩左边界
}
}
return min_size > s.size() ? "" : s.substr(min_l, min_size);
}
};
只有0和1的世界是简单的