算法题解:找出包含给定字符的最小窗口(枚举的通常方法)

题目分析

题目连接: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

相关文章
相关标签/搜索