[Leetcode] - Minimum Window Substring

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);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章