题目描述
给定两个字符串 S 和T,求 S 中包含T 所有字符的最短连续子字符串的长度,同时要求时间 复杂度不得超过O(n)。
解题思路
需要思考以下四个问题:
1、当移动 right
扩大窗口,即加入字符时,应该更新哪些数据?
2、什么条件下,窗口应该暂停扩大,开始移动left
缩小窗口?
3、当移动 left
缩小窗口,即移出字符时,应该更新哪些数据?
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
如果一个字符进入窗口,应该增加 window
计数器;如果一个字符将移出窗口的时候,应该减少 window
计数器;当 cnt
满足 need
时应该收缩窗口;应该在收缩窗口的时候更新最终结果。
需要注意的是,当我们发现某个字符在window
的数量满足了need
的需要,就要更新 valid,表示有一个字符已经满足要求。而且,你能发现,两次对窗口内数据的更新操作是完全对称的。
当 cnt == need.size()
时,说明T
中所有字符已经被覆盖,已经得到一个可行的覆盖子串,现在应该开始收缩窗口了,以便得到「最小覆盖子串」。
移动 left
收缩窗口时,窗口内的字符都是可行解,所以应该在收缩窗口的阶段进行最小覆盖子串的更新,以便从可行解中找到长度最短的最终结果。
AC
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int> need,window;
for(char c : t) need[c]++;
int left=0,right=0,cnt=0;
// 记录最小覆盖子串的起始索引及长度
int start=0,len=INT_MAX;
while(right<s.size()){
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if(need.count(c)){
window[c]++;
if(window[c]==need[c])
++cnt;
}
// 判断左侧窗口是否要收缩
while(cnt==need.size()){
// 在这里更新最小覆盖子串
if(right-left<len){
len=right-left;
start=left;
}
// dd 是将移出窗口的字符
char dd = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if(need.count(dd)){
if(window[dd]==need[dd]) --cnt;
window[dd]--;
}
}
}
return len == INT_MAX? "" : s.substr(start,len);
}
};
学如逆水行舟,不进则退