LeetCode 筆記系列16.3 Minimum Window Substring [從O(N*M), O(NlogM)到O(N),人生就是一場不停的戰斗]


題目: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.

前面兩個系列講了O(N*M)和O(NlogM)的解法。下面講一下O(N)的。人生可不就是一場不停的戰斗么。。。?

實際上leetcode已經有這個的詳細解法和介紹了,大家可以看看這里

以下部分我翻譯自leetcode上的解說。

注意到前面的解法(O(NlogM))用到了一個hash table,一個隊列(二娃:C++解法中使用隊列來實現charAppearanceRecorder),一個sorted map,真是要有多復雜有多復雜。在面試中,問題通常來說比較短,代碼也大多會不超過50行,所以一定要和面試官交流,讓他知道你的想法。如果你的思路比較復雜,面試官可能會給出一些提示。如果你半天都找不到好的方法又悶聲不響地在那摳,那就悲劇鳥。

我們下面使用 S = "acbbaca" and T = "aba" 來演示這個算法。基本思路是在遍歷S的過程中,使用兩個指針(合法window的begin和end索引)和兩個table(needToFindhasFound),needToFind保存T中每個字母的個數(二娃:相當於我們的needToFill),hasFound保存當前收集到的每個字母的個數。我們也同時使用一個count變量,用來保存當前收集到的字母總數,但是那些收集過了的字母數並不算在count里面。這樣的話,當count等於T.length,那我們就知道遇到一個合法的window了。

我們利用end指針來遍歷S,假設當前end指向S中的字母x,如果x是T中的字母,hasFound[x]加一。如果hasFound[x]目前還小於等於needToFind[x](二娃:說明字母x還沒有收集全或者剛剛收集全哦),那么我們同時也增加count。當合法window的條件滿足,也就是count等於T.length,我們立即遞增begin指針,並且保證在遞增的過程中始終有count等於T.length。

在遞增begin指針的過程中,我們怎么樣才能保證“始終有count等於T.length”呢?

假設begin指向字母x,如果hasFound[x]大於了needToFind[x],hasFound[x]減去一,同時遞增begin。(二娃:這里很有畫面感哦。因為當前遇到的x是冗余的靠左的字母,這里的操作其實等價於前面兩個算法中的“刪除charAppearanceRecorder中相應的字母的鏈表頭節點”,有點兒像一個是lazy去重,一個是eager去重)否則的話,當前的begin就是window的起始索引了。

接下來我們就可以通過end - begin + 1得到當前window的長度了。這里便可以更新最小window長度。

算法實際上首先找到第一個合法的window,然后在接下來的掃描過程中保持window的合法性(二娃:其實就是count 始終小於等於(當遇到新window)T.length)。

看下面的圖圖。

i)S = "acbbaca" and T = "aba".

ii)找到第一個合法的window。這里注意我們不能遞增begin指針因為hasFound['a'] 等於 needToFind['a'],即2. 如果我們此時遞增begin,那就不是合法window了。

iii)找到第二個合法的window。begin指針指向第一個a,hasFound['a']等於3,而needToFind['a']等於2,說明這個a是一個冗余的a,我們遞減hasFound['a']同時遞增begin。

iv)我們也需要跳過那些不在T中的字母,例如上面的c。現在beging指向了b,hasFound['b']等於2,大於了needToFind['b'],說明這也是一個冗余的b,我們遞減hasFound['a']同時遞增begin。

v)begin指向b,這時候hasFound['b']等於needToFind['b']。不能再減了,同時begin也不能再次移動了,這里就是一個短window的起點位置。

begin和end都最多前進N次,從而整個算法執行小於2N. 復雜度是O(N)。

上述算法的代碼如下:

 1 public String minWindow3(String S, String T){
 2         HashMap<Character, Integer> needToFill = new HashMap<Character, Integer>();
 3         HashMap<Character, Integer> hasFound = new HashMap<Character, Integer>();
 4         int count = 0;
 5         for(int i = 0; i < T.length(); i++){
 6             if(!needToFill.containsKey(T.charAt(i))){
 7                 needToFill.put(T.charAt(i), 1);
 8                 hasFound.put(T.charAt(i), 0);
 9             }else {
10                 needToFill.put(T.charAt(i), needToFill.get(T.charAt(i)) + 1);
11             }
12         }
13         int minWinBegin = -1;
14         int minWinEnd = S.length();
15         for(int begin = 0, end = 0; end < S.length(); end++){
16             char c = S.charAt(end);
17             if(needToFill.containsKey(c)){
18                 hasFound.put(c, hasFound.get(c) + 1);
19                 if(hasFound.get(c) <= needToFill.get(c)){
20                     count++;
21                 }
22                 if(count == T.length()){
23                     while(!needToFill.containsKey(S.charAt(begin)) ||
24                             hasFound.get(S.charAt(begin)) > needToFill.get(S.charAt(begin))) {
25                         if(needToFill.containsKey(S.charAt(begin)) 
26                                 && hasFound.get(S.charAt(begin)) > needToFill.get(S.charAt(begin))){
27                             hasFound.put(S.charAt(begin), hasFound.get(S.charAt(begin)) - 1);
28                         }
29                         begin++;
30                     }
31                     if(end - begin < minWinEnd - minWinBegin){
32                         minWinEnd = end;
33                         minWinBegin = begin;
34                     }
35                 }
36             }
37         }
38         return minWinBegin == -1 ? "" : S.substring(minWinBegin, minWinEnd + 1);
O(n)

這個算法的亮點是只用一個整型變量就判斷了是否是一個合法window。

該算法教育我們:

1.做題要有畫面感,而且是精確的畫面感;

2.如果用了超過2個復雜數據結構,應該考慮是否有更簡單的思路。;

3.人生是一場不停的戰斗,不斷優化你的算法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM