一般用于获取数组中“满足某条件C”的最长/短/等于某长度的区间。滑动窗口算法维护两个指针l,r,利用lr寻找满足条件C的区间[l,r)。lr移动方向相同,形成了一个「窗口」在直线上「滑动」的效果。
如果区间的寻找问题能化为 “有一个条件C(一般都为题目要求的条件),当满足C(不满足C)时,对区间的操作应为l右移;当不满足C(满足C)时,对区间的操作应为r右移”,则可用滑动窗口。
即可以根据条件C可将当前行动分为 r右移 or l右移二者中的一个
if 满足条件C(or不满足条件C): 应进行的操作 = l右移 else: 应进行的操作 = r右移
一般而言,这种“l能右则r不能右,r能右则l不能右”的性质是由"最长\短"和“要求C”共同作用导致的。
将 要求C 和 最长/短 看作两个平等的要求,这两个要求 一个希望l越右越好 r越左越好;一个希望l越左越好 r越右越好。可以发现这两个要求的希望是矛盾的,由于要同时满足这两个条件,导致我们不能直接把两个指针一个放最左一个放最右。
但正是这种矛盾的性质使得问题满足“l能右则r不能右,r能右则l不能右”。以具体问题为例:
以上分析的是“最长/短”的要求。采用滑动窗口如上所述,是很自然的。对于要求等于某长度的,可以在长度变化的过程中进行判断,看何时长度等于要求值,具体见后。(e.g. lc438 lc567)
l, r = 0, 0 #初始化为0,当前区间[l, r)为空,区间长度始终为 r-l while r < len(s): # 保证r不出界 ... # 对状态做修改(上一个轮执行了r右移,状态改变了),好让程序在后面检测是否满足条件 r += 1 # 利用大循环完成r指针右移,这一步其实放在小循环结束后也可以,影响不大,注意长度计算、数组索引取对就行 while 使得l右移的条件,也是使得r右移条件的反 and l < r: # 注意这里刚经过 r += 1,也就是说刚遍历过元素为 s[r-1],而不是 s[r]. 这里可能会需要l < r min_len = min(min_len, r-l) # 最小值 ... # 对状态做修改(上一轮小循环执行了l右移,状态改变了),好让程序在后面检测是否满足条件 l += 1 # 利用小循环完成l指针右移 max_len = max(max_len, r-l) # 最大值
recall:
由于“唯一性”,那么我们要做的就是用r遍历所有元素
(这样可以找到所有符合要求的区间),对于r指向的每个位置找到符合要求的区间
(满足要求C,这里先不管长度的要求),然后比较这些区间的长度
,获得最长/短(在这里处理对长度的要求)。
整个程序采用大小两个while循环,任务分别为:
r<len(s)
时大循环while不断右移r,以扩张区间;——用r遍历所有元素
对于r指向的每个位置找到符合要求的区间
根据两个循环的任务,写法如下:
r+=1
,利用小循环完成l右移l+=1
。(因为r用于扩张,所以必然先执行r右移,采用大循环。)r小于len(s)
,小循环写使得l右移的条件 and l小于r
(理论上这里需要控制l小于r,但是’l小于r’有时必然满足,这里忽略掉了,但写了肯定是没错的)
一般题目都会要求区间最长or最短(recall:当求最短区间时,l右移的条件=C;当求最长区间时,l右移的条件=C的反)
小循环用于右移l收缩区间,除了获得最小值外,对于每次收缩结束都想要进行的操作也应该在小循环内部完成:
对于求最短长度的问题,一般将minLen设置成某最大值,但是不像求最长区间可以直接返回maxLen。最短长度的问题需要先判断下minLen是否=最开始设定的最大值(此时说明符合条件的区间不存在)再返回。
看懂上述分析后,你就可以去秒杀lc上滑动窗口的题啦。
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
class Solution: def lengthOfLongestSubstring(self, s: str) -> int: l, r = 0, 0 cur_len, max_len = 0, 0 from collections import Counter cnt = Counter() while r < len(s): cnt[s[r]] += 1 r += 1 while cnt[s[r-1]] > 1: cnt[s[l]] -= 1 l += 1 max_len = max(max_len, r-l) return max_len
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
class Solution: def minWindow(self, s: str, t: str) -> str: l, r = 0, 0 start, min_len = 0, len(s)+1 from collections import Counter cnt = Counter(t) def contain_all_t(cnt): for itm in cnt.values(): if itm > 0: return False return True while r < len(s): if s[r] in cnt.keys(): cnt[s[r]] -= 1 r += 1 while contain_all_t(cnt): if min_len > r-l: min_len = r - l start = l if s[l] in cnt.keys(): cnt[s[l]] += 1 l += 1 # print(start, min_len) if min_len == len(s) + 1: return '' else: return s[start:start+min_len]
给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
class Solution: def minSubArrayLen(self, target: int, nums: List[int]) -> int: l, r, cur_sum, lenght = 0, 0, 0, len(nums) min_len = lenght + 1 while r < lenght: cur_sum += nums[r] r += 1 while cur_sum >= target: min_len = min(min_len, r - l) cur_sum -= nums[l] l += 1 if min_len == lenght + 1: return 0 else: return min_len
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
class Solution: def findAnagrams(self, s: str, p: str) -> List[int]: from collections import Counter def is_morethan_p(cnt): # is_all_zeors = True for itm in cnt.values(): if itm > 0: return -1 elif itm < 0: is_all_zeors = False if is_all_zeors: return 0 else: return 1 cnt = Counter(p) res = set() l, r = 0, 0 length, len_p = len(s), len(p) while r < length: if s[r] in cnt: cnt[s[r]] -= 1 r += 1 while is_morethan_p(cnt) >= 0 and l < r: if is_morethan_p(cnt) == 0 and r-l == len_p: res.add(l) if s[l] in cnt: cnt[s[l]] += 1 l += 1 # print('out',end=' ') # print(s[l:r]) return list(res)
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。
class Solution: def characterReplacement(self, s: str, k: int) -> int: def how_many(cnt): # print(cnt, end = ' ') # print(sum(cnt.values()), cnt.most_common(1)[0][-1]) return sum(cnt.values()) - cnt.most_common(1)[0][-1] from collections import Counter cnt = Counter() l, r, length = 0, 0, len(s) max_len = 0 while r < length: cnt[s[r]] += 1 r += 1 while how_many(cnt) > k and l < r: cnt[s[l]] -= 1 l += 1 max_len = max(max_len, r - l) return max_len
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。
class Solution: def checkInclusion(self, s1: str, s2: str) -> bool: from collections import Counter len_s1, len_s2 = len(s1), len(s2) l, r = 0, 0 cnt = Counter(s1) while r < len_s2: if s2[r] in cnt: cnt[s2[r]] -= 1 r += 1 while all([itm <= 0 for itm in cnt.values()]): if r - l == len_s1: return True if s2[l] in cnt: cnt[s2[l]] += 1 l += 1 return False
以上就是滑动窗口的全部内容了,有任何疑问欢迎留言!