滑动窗口本质上是解决子串问题!!!!
滑动窗口的算法流程如下:
1、左闭右开区间[left, right)
称为一个「窗口」,先不断地增加right
指针扩大窗口[left, right)
,直到窗口中的字符串符合要求(包含了T
中的所有字符)。
2、此时,我们停止增加right
,转而不断增加left
指针缩小窗口[left, right)
,直到窗口中的字符串不再符合要求(不包含T
中的所有字符了)。同时,每次增加left
,我们都要更新一轮结果。
3、重复第 2 和第 3 步,直到right
到达字符串S
的尽头。
举例说明:
重点:
在整个滑动过程中,我们涉及到的变量有:
字典:need ,将t中所有元素出现的个数存入字典中。
need = collections.defaultdict(int) for c in t: need[c]+=1
字典:window,将滑动窗口中符合t中元素的个数存入字典中,初始化全为0
window = dict(zip(t, [0] * len(t)))
窗口变量:起始下标 start = 0 和当前窗口的最小长度 minlen = len(s) + 1,给minlen一个绝对达不到的长度
滑动变量:左下标left, 右下标right,以及判断何时开始收缩左下标的变量valid = 0
思路:
因为区间是左闭右开,所以跳出循环的条件是right == len(s), 也就是说,进入循环的条件是
while right < len(s):
进入循环后,首先移动right,当下标所在元素s[right]是在need中出现了,window中对应的元素值加一,如果加一后,need对应的元素值和window的值相等,说明满足了某个元素的出现次数,让valid加一。
if s[right] in need:
window[s[right]] += 1
if window[s[right] == need[s[right]]:
valid += 1
right += 1
当窗口内的全部元素出现次数都满足要求,就要开始收缩left,直到窗口不满足要求。同时要对start和minlen进行维护,窗口的收缩和扩张一直到s的结尾,但是最后输出的元素要满足最小子串的要求。所以,每当出现一次满足窗口,我们都要和原来的minlen比较一次。
if right - left < minlen:
start = left
minlen = rigth - left
while valid == len(need):
if s[left] in need:
if window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
最后跳出循环之后,我们要进行一个判断,如果minlen的值没有改变,证明根本没有满足的窗口出现过,返回'' '',否则,我们返回s[start:start+minlen]
return s[start:start+minlen] if minlen != len(s) + 1 else ' '
以下是完整代码:
class Solution:
def minWindow(self, s: str, t: str) -> str:
import collections
need = collections.defaultdict(int)
for c in t:
need[c] += 1
window = dict(zip(t,[0]*len(t)))
right = 0
left = 0
valid = 0
start = 0
minlen = len(s) + 1
while right < len(s):
if s[right] in need:
window[s[right]] += 1
if window[s[right]] == need[s[right]]:
valid += 1
right += 1
while valid == len(need):
if right - left < minlen:
start = left
minlen = right - left
if s[left] in need:
if window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
return s[start:start+minlen] if minlen != len(s) + 1 else ''
把题目拆解一下,就是找到包含s1的最小子串,并且长度要和s1相等,如果没有就返回False
全部代码:
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
import collections
need = collections.defaultdict(int)
for c in s1:
need[c] += 1
window = dict(zip(s1,[0]*len(s1)))
right = 0
left = 0
valid = 0
start = 0
minlen = len(s2) + 1
while right < len(s2):
if s2[right] in need:
window[s2[right]] += 1
if window[s2[right]] == need[s2[right]]:
valid += 1
right += 1
while valid == len(need):
if right - left < minlen:
start = left
minlen = right - left
if s2[left] in need:
if window[s2[left]] == need[s2[left]]:
valid -= 1
window[s2[left]] -= 1
left += 1
return True if minlen != len(s2) + 1 and len(s2[start:start+minlen]) == len(s1) else False
把题目拆解一下就是,找到所有的匹配的子串,并且当子串的长度与p相同时,输出其起始索引。
全部代码:
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
import collections
need = collections.defaultdict(int)
for i in p:
need[i] += 1
window = dict(zip(p,[0]*len(p)))
start = []
length = len(p)
right = 0
left = 0
valid = 0
while right < len(s):
if s[right] in need:
window[s[right]] += 1
if window[s[right]] == need[s[right]]:
valid += 1
right += 1
while valid == len(need):
if right - left == length:
start.append(left)
if s[left] in need:
if window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
return start
由于没有事先知道的need,所以我们只需要维护窗口window一个字典,要求无重复字符的最长子串,那我们把全部字符存入字典中,初始值为0. right开始移动,每次移动都把window中对应元素的键值加一,如果该值大于1,证明有重复元素,让valid = 1。开始移动left,将window中对应的元素键值减一,而且要维护left的移动条件,如果window中对应元素大于1,valid -= 1。 移动完left之后(跳出了第二个循环),开始计算此时的window长度,选取最大长度作为length。最后返回length。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
window = dict(zip(set(s),[0]*len(set(s))))
rigth = 0
left = 0
length = 0
valid = 0
while rigth < len(s):
window[s[rigth]] += 1
if window[s[rigth]] > 1:
valid = 1
rigth += 1
while valid > 0:
if window[s[left]] > 1:
valid -= 1
window[s[left]] -= 1
left += 1
if rigth - left > length:
length = rigth - left
return length
用一个固定的滑动窗口,窗口的左边界是left = 0,右边界是right = length - 1,固定right之后,让left每次移动len(word[0])个长度,同样是window中元素的值与need中元素的值相同时,记录下这个窗口的左边界。然后让right+=1,重新确定窗口的左边界left = right - length + 1,再次移动。直到right == len(s)。
以下是全部代码:
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
import collections
need = collections.defaultdict(int)
for i in words:
need[i] += 1
window = dict(zip(words, [0] * len(words)))
length = len(words[0])*len(words)
right = length - 1
left = 0
valid = 0
start = []
t = s + '0'*(len(s)%len(words[0])) + '0'*len(words[0]) #这里是为了补全s,并且加长,防止下标出界。但是我感觉题目用的示例都没有这个问题,可写可不写。
while right < len(s):
left = right - length + 1
valid = 0
window = dict(zip(words, [0] * len(words))) #每次重新建立窗口,都要将valid和窗口元素值置0,同时重新计算左边界起始位置。
while left <= right:
if t[left:left+len(words[0])] in need: #满足条件时
window[t[left:left+len(words[0])]] += 1
if window[t[left:left+len(words[0])]] == need[t[left:left+len(words[0])]]:
valid += 1
if valid == len(need):#攒齐一波
start.append(right - length + 1)
valid = 0
window = dict(zip(words, [0] * len(words)))
break #每次窗口的大小就是一个正确答案的大小,所以如果该窗口是,那就一定是,不是,那就一定不是,确定之后就跳出到下一个窗口,并且都置0.
left += len(words[0]) #窗口内每次滑动一个单词大小长度
right += 1 #窗口外每次滑动一个字母
return start