算法題解:找出包含給定字符的最小窗口(枚舉的通常方法)

題目分析

題目連接:https://leetcode.com/problems...算法

題目補充:t能夠包含重複的字符,若是t包含了n個c,那麼找出的window也要包含n個c。

窗口是由2個遊標肯定的,咱們應該合理地移動遊標,枚舉出全部包含給定字符的窗口,而後返回其中寬度最小的。數組

如何使咱們的枚舉可以不重複、不遺漏呢?要作到不重不漏地枚舉,咱們須要爲每一種可能枚舉出的元素定義一種「大小判斷」,而後定義「如何從一個元素求出稍微大一些的下一個元素」(從當前的枚舉遷移到下一個枚舉)。最後,咱們只要從最小到最大按序枚舉,就可以保證不重不漏。app

具體到這一題,咱們要注意到如下特徵:每一個包含全部給定字符的窗口能夠由它的慢遊標惟一地指定。固定了慢遊標之後,咱們很容易就能夠找出快遊標應該在什麼位置。好比,能夠先看看下面這幅圖:spa

加入將慢遊標固定在C前面的位置(紅色),那麼快遊標只能在H後面的那個位置,才能包含全部題目要求的字符。(固然,你能夠說,將紅色遊標繼續向右移動,獲得的窗口也符合題意。可是這樣作沒有任何收益,反而將窗口的寬度變大了,所以咱們不考慮這種窗口)
所以,慢遊標的位置就能夠做爲一種比較窗口的標準。將慢遊標從左往右移動的過程當中,咱們遇到的窗口愈來愈「大」。code

假設咱們已經有了一個符合題意的窗口(紅色),要怎麼獲得下一個窗口(藍色)呢?
如圖所示,首先將慢遊標右移,使得窗口變「大」,而後移動快遊標,使窗口符合題意。對象

代碼實現

class Solution
{
  public:
    string minWindow(string s, string t)
    {
        // need_to_appear記錄了當前window還缺乏哪些字符、缺乏多少次
        // unqualified_char_number記錄了當前window中還缺乏多少種字符
        // iterator_fast~iterator_slow 之間就是當前的window
        vector<int> need_to_appear(256, 0);
        int unqualified_char_number = 0;
        // 已經找到的最小window
        int min_window_begin = 0, min_window_end = 0;
        bool have_window = false;

        for (auto c : t)
        {
            // 初始化每一個字符還要出現的次數
            if (need_to_appear[c] == 0)
                unqualified_char_number++;
            need_to_appear[c]++;
        }

        for (int iterator_fast = 0, iterator_slow = 0; iterator_fast < s.size(); iterator_fast++)
        {
            // 將當前字符的need_to_appear次數減一
            if (--need_to_appear[s[iterator_fast]] == 0)
            {
                // 若是need_to_appear次數剛好變成0,說明當前window如今包含了足夠數量的字符s[iterator_fast]
                unqualified_char_number--;
                if (unqualified_char_number == 0)
                {
                    // 若是unqualified_char_number剛好變成0,說明window如今包含了全部須要的字符

                    // 向前移動iterator_slow,直到當前window剛好包含全部須要的字符
                    // 這一步能夠將不須要的字符排出window
                    while (++need_to_appear[s[iterator_slow]] <= 0)
                    {
                        iterator_slow++;
                    }
                    // 比較當前window與已經找到的最小window,看看哪個更小
                    if (!have_window || iterator_fast - iterator_slow < min_window_end - min_window_begin)
                    {
                        min_window_begin = iterator_slow;
                        min_window_end = iterator_fast;
                        have_window = true;
                    }

                    // iterator_slow向後移動,使window再也不包含全部須要的字符
                    iterator_slow++;
                    unqualified_char_number++;
                }
            }
        }
        if (!have_window)
            return "";
        else
            return s.substr(min_window_begin, min_window_end - min_window_begin + 1);
    }
};

算法的時間複雜度爲O(n)。可能會人覺得「代碼中有嵌套循環,時間應該不是線性的」。然而,嵌套的while循環只是將慢遊標接着上次的位置向右移,總共移動的次數不會超過s的長度。blog

另一點值得注意的是,爲了在O(1)時間內查詢t中某個字符的信息(某個字符是否是在t中、還須要在窗口出現多少次),咱們使用了一種哈希表的想法——need_to_appear,只不過這個哈希表直接使用字符自己做爲鍵,所以不會出現衝突,而且保證能在O(1)時間內查詢到。
這種直接用存儲對象標識符做爲鍵的方法只有在標識符種類很少的狀況下使用。在這題,咱們假設可能出現的字符只是0~255,加入全部Unicode中的字符都有可能出現,那麼這種方式再也不合理(須要建立多於65536個字符的數組)。ip

相關文章
相關標籤/搜索