Minimum Window Substring

一. 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 empty string “”.

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

Difficulty:Hard

TIME:40MIN

解法

這道題挺有意思,要求包含子串的最小窗口。如果子串字符不重複,那當然很簡單,只需要不斷更新每一個字符的位置,然後記錄最大下標的字符和最小下標的字符之前下標的差值就可以了。

如果子串字符可以重複,也可以採用類似的方法,比較直觀的做法就是採用雙端隊列,如果字符數超過允許出現的字符數,就從隊列中彈出首元素,然後插入當前下標。

string minWindow(string s, string t) {
    vector<int> m(128, 0); //採用vector記錄字符比map要快,反正字符值也不會超過128
    vector<deque<int>> d(128);  //每一個字符維護一個自己的雙端隊列
    set<char> u(t.begin(), t.end()); //爲了查找方便使用一個set
    for(int i = 0; i < t.size(); i++)
        m[t[i]]++;
    int num = 0;
    int len = s.size();
    string result = "";
    for(int i = 0; i < s.size(); i++) {
        if(m[s[i]] != 0) {
            d[s[i]].push_back(i);
            if(d[s[i]].size() > m[s[i]])
                d[s[i]].pop_front(); //彈出頭元素
            else
                num++; //記錄出現了多少個子串中的字符
            if(num == t.size()) {
                int left = INT32_MAX,right = -1;
                /*更新left和right的值*/
                for(auto it = u.begin(); it != u.end(); it++) {
                    if(d[*it].front() < left)
                        left = d[*it].front();
                    if(d[*it].back() > right)
                        right = d[*it].back();
                }
                if(len >= right - left + 1) {
                    len = right - left + 1;
                    result = s.substr(left, len);
                }
            }
        }
    }
    return result;
}

代碼的時間複雜度爲O(n)

優化

寫完代碼之後才發現,我們只需要知道最左邊的字符下標left和最右邊的字符下標right就可以了,而之所以採用雙端隊列,就是因爲如果彈出的恰好是left下標,那麼我們應該重新在隊列中找到一個新的left下標來代替。

不過,我們可以利用map本身來幫我們完成這樣的工作,這種解法的思路採用一個例子來描述就是:

  • 比如對於字符s爲acbeba,字符串t爲ab,num爲出現的有效字符數目
  • 根據t來初始化map
  • 這個時候acbeba的map對應值爲101011
  • 遍歷下標0,這個時候m[a]–,num++,left爲0,map對應值變爲001010
  • 遍歷下標1,這個時候m[c]–,map對應的值變爲0-11010
  • 遍歷下標2,這個時候m[b]–,num++,map對應值變爲0-10000,num爲2,說明找到一個窗口,首先讓left變得有效,計算第一個窗口爲acb,計算完成後,捨棄left,並讓left對應的字符在map中的值加一,因此left爲1,map對應的值變爲1-10001(這一步是這個解法的關鍵所在,因爲必須捨棄left,才能讓窗口變得更小
  • 遍歷下標3,這個時候m[e]–,map對應值變爲1-10-101
  • 遍歷下標4,這個時候m[b]–,map對應值變爲1-1-1-1-11
  • 遍歷下標5,這個時候m[a]–,num++,map對應的值變爲0-1-1-1-10,num爲2,說明找到一個窗口,首先讓left變得有效,其餘操作和遍歷下標2的時候相同。
string minWindow(string s, string t) {
    vector<int> m(128, 0); //這個m表示我需要的字符大於0,不需要的字符等於0
    for(int i = 0; i < t.size(); i++)
        m[t[i]]++;
    int num = 0;
    int len = s.size();
    string result = "";
    int left = 0;
    for(int i = 0; i < s.size(); i++) {
        if(m[s[i]] > 0) //必須得找到需要的字符,num才加一
            num++;
        m[s[i]]--;
        if(num == t.size()) {
            while(m[s[left]] < 0) { //這一步是爲了找到有效的left,有效的left肯定有m[s[left]] == 0
                m[s[left]]++;
                left++;
            }
            if(len >= i - left + 1) {
                len = i - left + 1;
                result = s.substr(left, len);
            }
            /*捨棄left,num的值減一,現在我們需要尋找到另一個s[left]*/
            m[s[left]]++;
            left++;
            num--;
        }
    }
    return result;
}

代碼的時間複雜度爲O(n)

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