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.
注意:如果一個字母在T中出現了k次,那么在窗口中該字母至少出現k次
分析:暴力解決可以在O(n^2)時間復雜度內完成:遍歷S,當碰到T中的首字母時,以該字母為窗口起始位置找到一個最小窗口包含所有T中的字符,最后取所有窗口的最小值。在暴力方法中我們做了很多重復的事情,比如S = “aabc”,T = “abc”,當我們以第一個a為窗口起始位置遍歷時,我們要判斷b、c是否在T中,當我們以第二個a為窗口起始位置遍歷時,我們還要判斷b、c是否在T中。那么怎樣避免這種重復的工作呢? 本文地址
可以利用兩個指針掃描(兩個指針分別為start,i),以S = “e b a d b a c c b”(忽略空格),T = “abc”為例:
0 1 2 3 4 5 6 7 8
- 初始化 start = i = 0
- i 逐漸往后掃描S直到窗口S[start…i]包含所有T的字符,此時i = 6(字符c的位置)
- 縮減窗口:此時我們注意到窗口並不是最小的,需要調整 start 來縮減窗口。縮減規則為:如果S[start]不在T中 或者 S[start]在T中但是刪除后窗口依然可以包含T中的所有字符,那么start = start+1, 直到不滿足上述兩個縮減規則。縮減后i即為窗口的起始位置,此例中從e開始窗口中要依次刪掉e、b、a、d,start最后等於4 ,那么得到一個窗口大小為6-4+1 = 3
- start = start+1(此時窗口肯定不會包含所有的T中的字符),跳轉到步驟2繼續尋找下一個窗口。本例中還以找到一個窗口start = 5,i = 8,比上個窗口大,因此最終的最小窗口是S[4…6]
具體實現時,要用哈希表來映射T中字符以便在O(1)時間內判斷一個字符是否在T中,由於是字符緣故,可以用數組簡單的來實現;還需要一個哈希表來記錄掃描時T中的某個字符在S中出現的次數,也可以用數組實現
class Solution { public: string minWindow(string S, string T) { // IMPORTANT: Please reset any member data you declared, as // the same Solution instance will be reused for each test case. int lens = S.size(), lent = T.size(); int srcCnt[256] = {0};//T中每個字母的個數 int foundCnt[256] = {0};//當前找到T中每個字母的個數 for(int i = 0; i < lent; i++) srcCnt[T[i]]++; int hasFound = 0;//已經找到的字母數目 int winStart = -1, winEnd = lens;//窗口的左右邊界 //兩個指針start和i一起掃描 for(int i = 0, start = 0; i < lens; i++) if(srcCnt[S[i]] != 0) { foundCnt[S[i]]++; if(foundCnt[S[i]] <= srcCnt[S[i]])hasFound++; if(hasFound == lent) {//找到了一個滿足的窗口 while(srcCnt[S[start]] == 0 || foundCnt[S[start]] > srcCnt[S[start]]) { //縮減窗口 if(srcCnt[S[start]] != 0) foundCnt[S[start]]--; start++; } if(winEnd - winStart > i - start) { winStart = start; winEnd = i; } foundCnt[S[start]]--; start++; hasFound--; } } return winStart != -1 ? S.substr(winStart, winEnd - winStart +1) : ""; } };
小小改進:注意到上述步驟3中縮減窗口時start要跳過不在T中的字符,如果S = “eeeeeeeeebadbaccb”(S的前面有大量的字符e不在T中),T = “abc”,這個跳轉會很費時,如果可以在第2步i掃描的過程中保存好T中字符在S中出現的位置,那么我們在縮減窗口時就不需要跳過例子中大量的e,只需要跳過b、a這些在T中存在但是不影響窗口的字符。這個可以用輔助隊列來實現。
class Solution { public: string minWindow(string S, string T) { // IMPORTANT: Please reset any member data you declared, as // the same Solution instance will be reused for each test case. int lens = S.size(), lent = T.size(); queue<int> Q; int srcCnt[256] = {0};//T中每個字母的個數 int foundCnt[256] = {0};//當前找到T中每個字母的個數 for(int i = 0; i < lent; i++) srcCnt[T[i]]++; int hasFound = 0;//已經找到的字母數目 int winStart = -1, winEnd = lens;//窗口的左右邊界 for(int i = 0; i < lens; i++) if(srcCnt[S[i]] != 0) { Q.push(i); foundCnt[S[i]]++; if(foundCnt[S[i]] <= srcCnt[S[i]])hasFound++; if(hasFound == lent) {//找到了一個滿足的窗口 int k; do {//縮減窗口到最小 k = Q.front(); Q.pop(); foundCnt[S[k]]--; } while(srcCnt[S[k]] <= foundCnt[S[k]]); if(winEnd - winStart > i - k) { winStart = k; winEnd = i; } hasFound--; } } return winStart != -1 ? S.substr(winStart, winEnd - winStart +1) : ""; } };
【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3461301.html