滑动窗口的双指针用法一般用于解决子串问题,东哥的从我写了套框架,把滑动窗口算法变成了默写题中提到了相应的模板,我这里写一下python版本的:
def slidingWindow(s, t):
need = collections.defaultdict(int)
window = collections.defaultdict(int)
left, right, valid = 0, 0, 0
while right < len(s):
# c是要移入窗口的字符
c = s[right]
# 窗口右移
right += 1
# 窗口内数据更新
# debug位置
print("l,r:", left, right)
# 判断左侧窗口缩小的时机
while (window needs shrink):
# d是要移出去的元素
d = s[left]
# 窗口左边界右移,缩小
left += 1
# 数据更新逻辑
return XX
然后咱来看一道题:
76. 最小覆盖子串
这道题的主要思路详细的解说可以参加上面东哥的文章,我这里不再赘述。大概思路就是:先拓展窗口右边界使之找到可行解,在缩小左边界直至找不到解,在这之前最后的结果就是最优解,判断子串是否存在需要用到额外的map来进行记录对比,还需要用到Valid标志来监督是否处于有解状态:
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = collections.defaultdict(int)
window = collections.defaultdict(int)
# 构建目标子串的map,k为字符,v为个数
for c in t: need[c] += 1
# 两个指针都初始化到最左边,valid是满足need中k-v对的数量。
left, right, valid = 0, 0, 0
# 初始化
start, length = 0, float('INF')
while right < len(s):
# 取出窗口中最右边的一个字符,窗口右边滑动一位
c = s[right]
right += 1
# 如果当前的右边界的字符c属于目标子串t,记录命中的window_map对应元素value加一,如果某个K全部,valid计数加一
if need.get(c):
# 注意先加进来再判断
window[c] += 1
if window[c] == need[c]:
valid += 1
# 对应的字串在当前窗口中满足,判断左侧边界是否需要收缩,注意判断条件是len(need)
while valid == len(need):
# 更新最小覆盖子串
if right - left < length:
start = left
length = right - left
# d是要移出窗口的字符,移除d直到valid == len(need)无法满足
d = s[left]
left += 1
if need.get(d):
if window[d] == need[d]:
valid -= 1
# 注意先判断再移除
window[d] -= 1
if length == float("INF"):
return ""
else:
return s[start:start+length]
再看一题看看:
567. 字符串的排列
本题和上一题很像,只需要注意左边界缩小的条件以及返回的时候的判断条件即可:
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
need = collections.defaultdict(int)
window = collections.defaultdict(int)
for k in s1: need[k] += 1
left, right, valid = 0, 0, 0
while right < len(s2):
c = s2[right]
right += 1
if need.get(c):
window[c] += 1
if need[c] == window[c]:
valid += 1
# 注意此处的条件,是窗口长度大于等于子串长度
while right - left >= len(s1):
# 注意是len(need),不是len(s1),因为valid记录的是k-v都满足的个数
if valid == len(need):
return True
d = s2[left]
left += 1
if need.get(d):
if need[d] == window[d]:
valid -= 1
window[d] -= 1
return False
继续:
438. 找到字符串中所有字母异位词
在完全没想怎么做的情况下默写了下来:
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
need = collections.defaultdict(int)
window = collections.defaultdict(int)
for k in p: need[k] += 1
left, right, valid = 0, 0, 0
res = []
while right < len(s):
c = s[right]
right += 1
if need.get(c):
window[c] += 1
if need[c] == window[c]:
valid += 1
# 由于还是需要长度相等,所以这里还是判断窗口是不是大于目标子串长度
while right-left >= len(p):
if valid == len(need):
# 找到不返回记录结果
res.append(left)
d = s[left]
left += 1
if need.get(d):
if need[d] == window[d]:
valid -= 1
window[d] -= 1
return res
再来一道:
3. 无重复字符的最长子串
这题稍微有一点点不一样,但是用文章最开头的套路还是能解的,需要理解这个窗口扩大缩小的时机以及题目要求返回的东西,看一下代码:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
window = collections.defaultdict(int)
left, right = 0, 0
res = 0
while right < len(s):
c = s[right]
right += 1
# 窗口扩充
window[c] += 1
# 出现重复的就开始缩小窗口
while window[c] > 1:
d = s[left]
left += 1
# 窗口缩小,此时肯定是不包含重复元素的,所以当前窗口长度就是新的一个答案
window[d] -= 1
# 每次稳定窗口后都需要更新一下最大长度
res = max(res, right - left)
return res
至此,双指针的三种常规用法我们都看完了,需要时常拿出来复习哦。