题目分析

题目链接:https://leetcode.com/problems...

题目补充:t可以包含重复的字符,如果t包含了n个c,那么找出的window也要包含n个c。

窗口是由2个游标确定的,我们应该合理地移动游标,枚举出所有包含给定字符的窗口,然后返回其中宽度最小的。

如何使我们的枚举能够不重复、不遗漏呢?要做到不重不漏地枚举,我们需要为每一种可能枚举出的元素定义一种“大小判断”,然后定义“如何从一个元素求出稍微大一些的下一个元素”(从当前的枚举迁移到下一个枚举)。最后,我们只要从最小到最大按序枚举,就能够保证不重不漏。

具体到这一题,我们要注意到以下特征:每个包含所有给定字符的窗口可以由它的慢游标唯一地指定。固定了慢游标以后,我们很容易就可以找出快游标应该在什么位置。比如,可以先看看下面这幅图:

加入将慢游标固定在C前面的位置(红色),那么快游标只能在H后面的那个位置,才能包含所有题目要求的字符。(当然,你可以说,将红色游标继续向右移动,得到的窗口也符合题意。但是这样做没有任何收益,反而将窗口的宽度变大了,因此我们不考虑这种窗口)
因此,慢游标的位置就可以作为一种比较窗口的标准。将慢游标从左往右移动的过程中,我们遇到的窗口越来越“大”。

假设我们已经有了一个符合题意的窗口(红色),要怎么得到下一个窗口(蓝色)呢?
如图所示,首先将慢游标右移,使得窗口变“大”,然后移动快游标,使窗口符合题意。

代码实现

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的长度。

另外一点值得注意的是,为了在O(1)时间内查询t中某个字符的信息(某个字符是不是在t中、还需要在窗口出现多少次),我们使用了一种哈希表的想法——need_to_appear,只不过这个哈希表直接使用字符本身作为键,因此不会出现冲突,并且保证能在O(1)时间内查询到。
这种直接用存储对象标识符作为键的方法只有在标识符种类不多的情况下使用。在这题,我们假设可能出现的字符只是0~255,加入所有Unicode中的字符都有可能出现,那么这种方式不再合理(需要创建多于65536个字符的数组)。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.