Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
For example,
S = "ADOBECODEBANC"
T = "ABC"
Minimum window is "BANC"
.
Note:
If there is no such window in S that covers all characters in T, return the emtpy string ""
.
If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.
今天在看編程之美的時候看到了類似的題目(3.5 最短摘要的生成),所以把這道題拿出來複習,總結一下。
這個題目的思路是這樣的,首先定義兩個HashMap,其形式均爲HashMap<Character, Integer>。其中一個map_t用來記錄字符串T中每個字符出現的個數,這個需要做pre-processing。另外一個map_s,是動態變化的,裏面的內容隨着循環的進行而不斷的變化着。與此同時,我們還需要定義一個關鍵的變量用於記錄T中所有字符在當前S字符串的窗口中出現的總次數,這裏用appear來表示這個變量。也就是說,當appear的長度等於字符串T的長度時,我們可以確定,S字符串的當前窗口裏包含了字符串T的所有字符至少一次。注意是至少一次,因爲可能會有這種情況出現:比如S=ABCBD,T=ABD,這樣的話,最小窗口應該是“ABCBD”,可以看到B出現了兩次,而A和D各出現一次。那麼如何更新map_s的內容呢?對於每一個S中的字符c,我們先檢查c是否在map_t之中,若不在,則直接continue。若在,再檢查c是否在map_s中,若不在,將其put進去,然後appear的值加1,這是因爲T中每個字符必然至少出現一次。若c在map_s中,那麼只有當c在map_s中對應的value小於其在map_t中對應的value值時,才另appear加1,這是因爲,map_t中每個字符對應的value是這個字符在字符串S的窗口中應該要出現的次數。還需注意,除了更新appear之外,還有更新字符c在map_s中對應的value的值,這個值將用於對窗口的縮減。那麼怎麼縮小這個窗口呢?從最左邊開始,依次測試每個字符,若該字符在map_s中對應的value等於其在map_t中對應的value,則當前窗口已經縮至最小,記錄下當前窗口大小,繼續將右側指針右移。否則,則可以將左側指針左移以縮小窗口的大小。注意縮減窗口的同時更新map_s中相應字符對應的value值。最後,還要對S中不包含T的全部字符這種edge case做一下特殊處理,比如,可以用一個變量flag等。
可以看到,對於每個S中的字符c,這種做法最多將c加入窗口1次,且最多將c從窗口中刪除1次。因此,時間複雜度爲O(n)。
代碼如下:
public class Solution {
public String minWindow(String S, String T) {
if(S==null || T==null || S.length()<T.length()) return "";
// the hash map of string T
HashMap<Character, Integer> map_t = new HashMap<Character, Integer>();
for(int i=0; i<T.length(); i++) {
if(!map_t.containsKey(T.charAt(i))) map_t.put(T.charAt(i), 0);
map_t.put(T.charAt(i), map_t.get(T.charAt(i))+1);
}
HashMap<Character, Integer> map_s = new HashMap<Character, Integer>();
int min=Integer.MIN_VALUE, max=Integer.MAX_VALUE, left=0; // min, max->final result, left->left pointers
int appear = 0; // the number of characters of T appearance in the current substring of S, core logic
for(int right=0; right<S.length(); right++) {
char current = S.charAt(right);
// current character is contained by T
if(map_t.containsKey(current)) {
// first appear of this character in S, since it must appear at least one time in T, we can put 1 directly
if(!map_s.containsKey(current)) {
map_s.put(current, 1);
appear++;
}
// current character appears more times in T than in S
else {
if(map_s.get(current)<map_t.get(current)) appear++;
map_s.put(current, map_s.get(current)+1);
}
}
// current window contains all characters in T
// once appear is eqaul to T.length(), it cannot be reduced during the rest iteration
if(appear >= T.length()) {
// shrink the left pointer of the window
while(left < S.length()) {
char minChar = S.charAt(left);
// this character is contained by string T
if(map_t.containsKey(minChar)) {
// we should futhur determine whether we can remove it from the current window
if(map_s.get(minChar) > map_t.get(minChar)) {
map_s.put(minChar, map_s.get(minChar)-1);
left++;
}
else break;
}
// this character is not contained by string T, so we just increment the left pointer
else left++;
}
// update the window pointers if necessary
if(max==Integer.MAX_VALUE || max-min>right-left) {
max = right;
min = left;
}
}
}
if(min==Integer.MIN_VALUE) return "";
else return S.substring(min, max+1);
}
}