继续刷LeetCode 热题 HOT 100 的题目,并且在博客更新我的solutions。在csdn博客中我会尽量用文字解释清楚,相关Java代码大家可以前往我的个人博客jinhuaiyu.com中查看。
题目:最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
solution:滑动窗口
这道题如果我们用暴力破解,每次讨论以一个位置字符为初始字符的符合要求的子串,则需要O(n²)的时间复杂度。优化点在于如果找到一个覆盖子串,则在内层循环中其右边的字符就不用再遍历了(初始字符固定),因为长度更长的不是我们要求的。但这样时间复杂度还是没变。这个固定初始字符的最小覆盖子串是不是还能继续在外层循环的下一个(初始字符右移一位)继续用到?这样的话内层循环就不比再从初始字符重新遍历了。这就是滑动窗口在这道题的思想。
试想在字符串s上有一个滑动窗口,刚开始左指针在第一个字符处,右指针开始从左往右遍历,直到刚好到某个位置时,覆盖的字符包含字符串t中所有字符。此时得到一个可能的结果,我们记录下来。接下来右指针不动,左指针开始右移,直到刚好破坏了前面的条件(窗口中不再包含s所有字符),在左指针右移的过程中(不包括最好条件不成立时),会得到一些更小的覆盖子串,可以用来更新结果。
左指针不再移动,又开始右移右指针,直到达到某个位置时符合条件,接着又移动左指针,这个过程和前面一样。我们可以发现,在先移动右指针找到覆盖子串的尾部后,左指针移动时会找到所有符合条件的子串,这个过程中我们一直维护最小的覆盖子串。最终左右指针遍历完字符串s,找到最小覆盖子串,但这个过程中虽然有双循环(外循环移动右指针找到符合条件子串,内循环移动左指针尽可能缩小覆盖),但左右指针都只是从左往右移动了n位,这个过程的时间复杂度是O(n)。
如何在O(n)的时间复杂度内判断窗口是否包含t字符串呢?我们可以用两个hash表分别存储窗口中和字符串t中对应字符的数量(窗口的hash表没有必要存储t中没有的字符,减少存储空间),比较时可以用迭代器在O(n)的时间复杂度内对比两个hash表。
Finally,带有详细注释的代码放在我的个人博客http://jinhuaiyu.com/leetcode-minimum-window-substring/