03-滑动窗口
📅 发表于 2026/04/02
🔄 更新于 2026/04/02
👁️ -- 次访问
📝 0 字
⏳ 0 分钟
异位词
abc和bca为异位词判断counter是否相等即可。覆盖子串
abcd为覆盖abc的串。字符数量 大于等于。题目
连续最长不重复子串的长度。暴力法
每个字符作为起始,向后求解,计算当前子串长度。滑动窗口左右指针法
左右指针定义窗口子串[l,r],遍历r,求解每个窗口长度r-l+1 。窗口内无重复字符若s[r]出现过,左侧指针需要往前移动,到s[r]出现位置的下一个位置。l只能往前,需要取max,避免连续重复字符导致回退。字符出现位置。题目
长字符串s和短字符串p,求解s中p的所有字母异位子串,固定窗口大小。暴力法
s每个字符,作为窗口起始值 左指针,判断窗口子串和p是否异位词。 (Key)左右指针统计次数法
p和窗口子串的字符次数。右指针进入,计算窗口大小,旧左指针出去。计算窗口值。题目
nums数组和一个目标求和值k,计算和为k的连续子数组数量。前缀和哈希解法
前缀和 出现次数当前前缀和,减去K,计算需要前缀和需要前缀和的数量。题目
nums数组,和滑动窗口大小k。依次输出每个窗口内的最大值。双端单调队列滑动窗口队头最大值法
遍历right边界,计算left边界。单调递增。队首>队尾,队首作为窗口最大值,存储下标。队首过期出队:超出left边界。新队尾入队:保证为队列最小值;若旧队尾小于当前值,则需循环出队。题目
s中,覆盖t中所有字符的最短子串子串覆盖
窗口字符次数 >= t中字符次数滑动窗口右扩展左收缩找最短覆盖
遍历右指针,右指针进入窗口。更新窗口大小左指针循环收缩,找最小窗口。窗口不满足则停止收缩。右指针扩张右指针扩张。字符串s,可能有多个相同不同的字符最长不重复子串的长度。abcdbcd -> abcd -> 4;aaaa -> a -> 1遍历每个字符,依次计算其作为起始字符的最大长度。字符集合,依次往后找,如果某个字符出现过,则停止。def lengthOfLongestSubstring_force_loop(self, s: str) -> int:
""" 暴力遍历,从每个字符开始,往后查找
"""
if not s:
return 0
n = len(s)
max_length = 1
for i in range(n):
cur_sets = set()
cur_sets.add(s[i])
j = i + 1
while j < n and s[j] not in cur_sets:
cur_sets.add(s[j])
j += 1
max_length = max(max_length, len(cur_sets))
return max_length核心思想
左右指针,固定 左指针,遍历 右指针。当前窗口 [l, r]的长度: r-l+1。窗口内部 无重复字符r位置字符 s[r] 已出现过,则需要移动左指针l左指针l移动到:s[r]重复位置的下一个位置l新位置 需取max,不能往回走左指针不能往回走
abba例子,l=2, r=3, 但若直接下一个位置,则l=1,往回走,导致重复def lengthOfLongestSubstring_leftright_sliding_window(self, s: str) -> int:
if not s:
return 0
n = len(s)
# 左右双指针
l = r = 0
# 记录字符出现的位置
ch2pos = {}
max_len = 0
# 右指针往前走,左指针看情况往前走,每次循环计算一个窗口 [l,r]
while r < n:
# 当前窗口最右侧的字符
ch = s[r]
if s[r] in ch2pos:
# 需要保证 [l, r] 之间无重复字符
# 当前字符已出现过,l指针需往前移动到出现字符的下一个位置,但是不能往回走。
# 比如abba这个例子,l=2, r=3, 但若是直接下一个位置,则l=1,往回走,导致重复
l = max(ch2pos[ch] + 1, l)
# 更新位置
ch2pos[ch] = r
# 当前窗口长度
cur_len = r - l + 1
max_len = max(max_len, cur_len)
# 右指针继续往前走
r += 1
return max_len遍历s中每个字符,作为窗口起始值。判断当前窗口和p是否是异位词。异位词判断:排序后 生成字符串key,判断 key相同即可。会超时。def findAnagrams_force_loop(self, s: str, p: str) -> List[int]:
res = []
if not s or not p:
return []
# p的key和窗口大小
pkey = "".join(sorted(p))
window_size = len(p)
for i in range(len(s)):
j = i + window_size
if j > len(s):
break
# 当前窗口值
subs = s[i:j]
subs_key = "".join(sorted(subs))
if pkey == subs_key:
res.append(i)
return res窗口异位词判断
Counter()类,直接判断 cnt_p == cnt_s,即字符出现次数 是否相等。核心思想
p字符串、滑动窗口字符出现次数。遍历右指针 做循环右指针字符进入,新元素+1。left<0 :窗口大小不足,继续循环left>0:窗口溢出,左指针字符 出去,旧元素-1。计算当前窗口内容:cnt_p == cnt_s滑动窗口三字经
s_count[新元素] += 1if 长度超标: s_count[旧元素] -= 1if s_count == p_count: 记录答案def findAnagrams_leftright_window(self, s: str, p: str) -> List[int]:
res = []
if not s or not p or len(s) < len(p):
return res
# 直接统计p字符串以及窗口中的字符出现次数
cnt_p = Counter(p)
cnt_s = Counter()
for right, ch in enumerate(s):
# 右侧字符+1
cnt_s[ch] += 1
# 窗口左侧位置
left = right - len(p) + 1
if left < 0:
# 窗口大小不足
continue
if left > 0:
# 窗口起始位置>0,上一轮的左侧位置减1
pre_left = left - 1
cnt_s[s[pre_left]] -= 1
# 直接判断两个counter是否相似,自动忽视值为0的项
if cnt_p == cnt_s:
res.append(left)
return res两个定义
前缀和 pre[i]:数组元素位置0到i的求和
哈希计数:统计前缀和具体值的出现次数
核心思想
累计前缀和 current_sum、前缀次数 count、初始化count[0]=1含当前位置元素的前缀和pre[i]当前位置的补数:补数 = 前缀和 - 求和k补数存在,则次数 += 补数次数注意
并非单调递增,不能用 滑动窗口。示例
[1, 2, 3, 4, 5],K=7,正确应该是 [3, 4] 子数组前缀和:[1, 3, 6, 10, 15]补数前缀和:[-6, -3, -1, 3, 8]补数前缀和3 出现1次,则次数+1。把[1,2]去掉,剩下[3,4],则和为K=7def subarraySum(self, nums: List[int], k: int) -> int:
"""
前缀和+Hash记录次数
"""
if not nums:
return 0
# 累计前缀求和
current_sum = 0
# 前缀出现次数
count = defaultdict(int)
# 必须初始化前缀和0为1
# 因为 1, 2, 3。若是k=3,到1 2的时候,need_sum=0,正好1,2算一个
count[0] = 1
res = 0
for num in nums:
# 当前前缀和
current_sum += num
# 当前位置所需的前缀
need_sum = current_sum - k
if need_sum in count:
# 累加之前的所有统计值
res += count[need_sum]
# 更新当前前缀和
count[current_sum] += 1
return res双端队列
队首 > 队尾,每次窗口取队首作为最大值,队尾为最小值。存储下标,方便判断过期。滑动窗口核心思想
遍历右指针 做循环队首出队:队首过期,不在窗口内,小于left索引队尾出队:保证 队尾为最小值,需小于nums[r],循环出队当前元素nums[r] 入队当前left-right 满足窗口大小,则从队首取当前窗口最大值def maxSlidingWindow_queue(self, nums: List[int], k: int) -> List[int]:
""" 使用单调队列:保持先进先出单调递增,当前值超过队首最大值,则队列清空后再入队;当前值小于队尾值,则直接入队。队首超期,也出队
"""
res = []
if not nums or len(nums) < k or k <= 0:
return res
# 队列
queue = deque(maxlen=k)
# 循环遍历每个元素,作为右侧窗口值
n = len(nums)
for right in range(n):
# 滑动窗口左侧值
left = right - k + 1
# 1. 队首过期出队,不在滑动窗口里
if len(queue) > 0 and queue[0] < left:
queue.popleft()
# 2. 队尾检查,保证队尾元素大于当前值
while len(queue) > 0 and nums[queue[-1]] < nums[right]:
# 队尾出队
queue.pop()
# 3. 当前元素入队
queue.append(right)
# 4. 计算滑动窗口值
if left >= 0:
res.append(nums[queue[0]])
return res子串覆盖判断
窗口字符次数 >= 目标字符次数。大于等于很重要,并不只是等于。滑动窗口右扩展左收缩
先一直往右走,直到当前窗口包含最短子串。后一直往右走,直到当前窗口不包含最短子串。核心链路
右指针进入窗口右指针扩展更新窗口大小循环 左指针收缩,寻找最小窗口左指针继续收缩。右指针扩展def minWindow_lrwindow(self, s: str, t: str) -> str:
"""滑动窗口,右指针扩充,左指针收缩
"""
if not s or not t or len(s) < len(t):
return ""
# 字符串计数
cnt_w = defaultdict(int)
cnt_t = defaultdict(int)
for ch in t:
cnt_t[ch] += 1
# 左右指针
l = r = 0
def match_substr():
match_all = True
for ch, cnt in cnt_t.items():
# 窗口内的cnt需要大于等于目标cnt
if cnt_w[ch] < cnt:
match_all = False
break
return match_all
res = ""
while r < len(s):
# 右指针进入窗口
cnt_w[s[r]] += 1
# 判断当前窗口是否满足
match_all = match_substr()
if match_all:
# 当前窗口满足子串,计算更新窗口
if not res or r-l+1 < len(res):
res = s[l:r+1]
# 左指针向右收缩,寻找最小子串
while l+1 <= r:
cnt_w[s[l]] -= 1
l += 1
match_all = match_substr()
if match_all:
if r-l+1 < len(res):
res = s[l:r+1]
else:
break
# 右指针继续往前走
r += 1
else:
# 当前窗口不满足,右指针扩展
r += 1
return res