最小覆盖子串 滑动窗口
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路:
滑动窗口!滑动窗口!滑动窗口!找“子串”问题,我们十有八九就用滑动窗口。滑动窗口是一种非常常用的处理思路,包括其代码框架(看似很多很复杂,实际上万变不离其宗!用多了就是一个样)。能用滑动窗口解决的经典题目大概只有有五六个,但是他们又很高频!所以基本上都做过一遍后,一见到这个题,就知道怎么做了:开滑!
等待,什么是滑动窗口?就是一个宽度可以改变、位置可以改变且初始化在最左边的区间。
滑动窗口的滑动方式是固定的。就像“毛毛虫爬行“,先是右边界右移,窗口长度拉长;再是左边界右移,窗口长度缩小。重复这个过程直到窗口抵达最右边。
为什么要以这样的方式去滑动呢?结合这道题我们就理解了。我们首先定义一个长度为0的滑动窗口,在字符串的最左边,显然现在它包含0个字符,现在我们扩大右边界(右边界右移),扩大到多少呢——使得窗口包含题目要求的子串,一旦满足这个要求,我们就记录下这个窗口的初步长度;然后保持右边界不动,开始缩小左边界(左边界右移),缩小到多少呢——使得窗口恰好不满足包含题目要求的子串,而在此之前,每一次缩小,我们都记录和更新这个最小区间。缩小结束,我们再次扩大右边界……重复这个过程直到窗口抵达最右边。
可以看到,滑动窗口的思路就是以一个很聪明的(复杂度非常低的)方式去完成对字符串的遍历,不断地通过扩大右边界来初步满足结果,再通过缩小左边界来逼近最优解。
滑动窗口的整体逻辑如上,但一些实现细节还需要注意。对于这个题,题目中要求的t字符并不要求顺序,所以我们用字典的形式存起来即可。比如t="ABCA",则我们定义一个需求字典need={"A":2,"B":1,"C":1}。对于滑动窗口当前包含的字符,我们也定义一个字典has,存储方式类似。
代码:
class Solution(object):
def minWindow(self, s, t):
def tianjia(d,c):#向字典中添加元素的函数
if c in d:
d[c]+=1
else:
d[c]=1
left=0#初始化左边界
right = 0#初始化右边界
has={}#当前窗口中有的字母
need = {}#存放题目的需求,即需要包含哪些字符
lenth=len(s)
if lenth<len(t):#如果s的长度小于t,不存在覆盖子串,按题意返回"".
return ''
valid=0 #valid表示目前已经满足的字母数
res_len = float('inf') #初始化结果长度为正无穷,方便一步步缩小
res_left =0#存储结果区间的左边
res_right=0#存储结果区间的右边
for i in t:#先把t字符串添加进need字典
tianjia(need,i)
while(right<lenth):#在窗口抵达最右边之前
c = s[right]#窗口每扩大一点,就包含了一个新的元素
right+=1#扩大窗口(右移右边界)
if c in need:#如果这个新的元素在”need清单”里,证明这个元素值得被记录
#我们就把它放在has里
tianjia(has,c)
if has[c]==need[c]:#如果此时has里相应元素的个数正好等于need
valid +=1#证明这个元素满足条件了,valid+1
#当valid值等于need清单的长度,证明所有字符都满足条件了
while(valid==len(need)):
#此时要准备开始缩小窗口来逼近最优解了
#首先我们要做的就是记录和更新(如果区间长度是历史最小值的话)这个区间的位置
if right-left<res_len:
res_len = right-left #res_lne暂存结果区间的长度
res_left = left #res_left暂存结果区间的左边
res_right = right #res_right暂存结果区间的右边
#注意!!!!开始缩小窗口了
cc = s[left]#把左边界当前这个元素拿出,这个元素随着窗口收缩要除去
left+=1#缩小窗口(右移左边界)
if cc in need:#如果要除去的这个元素在”need清单”里
has[cc]-=1#在has字典里把相应值-1
#减完之后若恰好差1满足需求,说明减掉的这个字符起到了让窗口不再满足条件的作用
if has[cc]+1==need[cc]:
valid-=1#valid减少,即满足条件的元素减少了
return s[res_left:res_right]
小结:
题目中我定义了一个向字典d中添加元素c的函数tianjia(d,c),若原来没有则置1;若已有则+1,这样提前定义后,后续就不需要重复再写了,我感觉这样还是很方便的。
这段代码看似很长,细节很多,但实际上很好理解,且可复用性极高。整个滑动窗口,就是两层while循环: 外层while(right<length)扩大窗口,内层while(valid满足条件)缩小窗口。而扩大和缩小窗口,就是分别右移右边界和右移左边界而已。

浙公网安备 33010602011771号