滑动窗口
题目
给你一个字符串 s
、一个字符串t
。返回 s
中涵盖t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 “” 。
注意:如果 s
中存在这样的子串,我们保证它是唯一的答案。
示例1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例2:
输入:s = "a", t = "a"
输出:"a"
提示:
1 <= s.length, t.length <= 10^5
s 和 t 由英文字母组成
题解
滑动窗口思想
用l,r
表示滑动窗口的左边界和右边界,通过改变l,r
来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度r-l+1,这些长度中的最小值就是要求的结果。
步骤一:不断增加
r
使滑动窗口增大,直到窗口包含了T的所有元素步骤二:不断增加
l
使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值步骤三:让
l
再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j超出了字符串S范围。
判断窗口是否包含T中所有元素
用字典need
表示当前滑动窗口中需要的各元素数量,一开始滑动窗口为空,用T中各元素初始化need
,在滑动窗口扩展或者收缩时,维护该need
字典
- 当窗口包含某个元素,
need
中该元素数量减1,表示所需元素减少1个; - 当窗口移除某个元素,
need
中钙元素的数量加1
need
始终记录当前滑动窗口下,还需的元素数量,在改变l,r
时,需同步维护need
注意:只要某个元素包含在滑动窗口中,need
中就存储这个元素的数量,如果某个元素存储的是负数表示这个元素是多余的。如need={‘A’:-2,'C':1}
时,表示当前窗口中,2个A是多余的,同时还需要一个C。此目的是步骤二中排除不必要的元素,数量为负的就是不必要的元素,而数量为0表示刚刚好。
当need
中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素
优化
如果每次判断滑动窗口是否包含了T的所有元素,都去遍历need
看是否所有元素数量都小于等于0,这个会耗费O(k)
的时间复杂度,k代表字典长度,最坏情况下,k可能等于len(S)。
其实这个是可以避免的,我们可以维护一个额外的变量needCnt
来记录所需元素的总数量,当我们碰到一个所需元素c
,不仅need[c]
的数量减少1,同时needCnt
也要减少1,这样我们通过needCnt就可以知道是否满足条件,而无需遍历字典了。
前面也提到过,need
记录了遍历到的所有元素,而只有need[c]>0
大于0时,代表c
就是所需元素
图示
以S=”DOABECODEBANC”,T=”ABC”为例
初始状态:

- 步骤一:不断增加j使滑动窗口增大,直到窗口包含了T的所有元素,need中所有元素的数量都小于等于0,同时needCnt也是0

- 步骤二:不断增加i使滑动窗口缩小,直到碰到一个必须包含的元素A,此时记录长度更新结果

步骤三:让i再增加一个位置,开始寻找下一个满足条件的滑动窗口

1 | import collections |
1 | s = 'ADOBECODEBANC' |
BANC
复杂度
我们会用r
扫描一遍S
,也会用l
扫描一遍S
,最多扫描2次S,所以时间复杂度是O(n)
,空间复杂度为O(k)
,k为S和T中的字符集合。