题目网址:https://leetcode-cn.com/problems/minimum-window-substring/
题目分析
这道题目,明显之处在于,我们需要在字符串 s
中框出一个窗口,来判断这个窗口中的子串是否覆盖了 t
,如下图所示。就是要判断黄色窗口内的子串,有没有覆盖字符串 t
。
由此一来,我们需要解决两个很重要的问题。一是如何判断黄色窗口子串有没有覆盖 t
,二是如何得到滑动窗口。
如何判断是否覆盖
假设我们有两个字符串,分别为 s1 = "OBECOEB"
(上图中的黄色区域)和 t = "ABC"
。如何判断 s1
有没有覆盖 t
呢?
我们可以分别统计字符串 s1
和 t
中各字符的个数,得到下图。我们可以看到,t
中有 1 个 A
,而 s1
中没有,因此 s1
不能覆盖 t
。
再比如,有字符串 s2 = "BANC"
,t
包含的字符均在 s2
中出现,且 t
中各字符的数量,都小于等于 s2
中相应字符的数量。因此,s2
覆盖了 t
。
并且,我们只需要关注那些出现在 t
中的字符,可以忽视掉那些不出现在 t
中的字符。比如,s1
中的 E
和 O
,它没有出现在 t
中,我们可以忽略它。同时为了节省空间,我们实际上保存好 t
和 s1
各字符个数的差值就好。
我们只关心出现在 t
中的字符,把 t
中这些字符的个数减去 s1
中相应字符的个数,结果如下图。字符 A
的结果是正数,说明还有一个 A
没有被覆盖,因此字符串 s1
未能覆盖 t
。
但是,窗口是在不断变化的。如果每次变化窗口,都需要重新计算窗口中各字符的数量,且都要与 t
中相应字符的数量做差,这样做是很低效的。但事实上并非如此。
假设我们已经有了一个滑动窗口,窗口左、右下标分别为 l
和 r
。我们可以观察到,无论是向右移动 l
还是 r
,都是只有一个字符的改变。例如,把 l
向右移动一位,字符 O
减少一个。实际上,我们只要得到一个窗口的各字符数量与 t
的差值,就可以快速地得到下一个窗口与 t
各字符数量的差值。
再进一步。
现在我们可以得到 t
与各个窗口中字符的差值。这是不是意味着我们每得到一个窗口,都需要来看看差值中每个数都小于等于 0?
其实,我们可以使用空间换时间的方法,再用一个变量 cnt
来表示覆盖的数量。还是字符串 s1 = "OBECOEB"
和 t = "ABC"
,s1
实际上只覆盖 t
的 B
和 C
两个字符,此时 cnt
等于 2,小于 t
的长度。很明显,s1
未能覆盖 t
。
而窗口 s2 = "BANC"
的 cnt
为 3,恰好等于 t
的长度,因此 s2
覆盖了 t
。
到这里,我们已经解决了第一个问题,如何判断子串有没有覆盖。接下来解决第二个问题,如何得到滑动窗口。
滑动窗口
同样,我们用下标 l
和 r
来表示区间 [l, r]
中的子串。当然,我们可以枚举,把所有可能的子串都枚举出来,但这没有必要。
我们让 l
和 r
都从 0 出发,并且可以得到字符差值,如下图所示。
此时