題目描述
給定兩個字符串 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);
}
};
學如逆水行舟,不進則退