滑动窗口万能模板:从基础到双窗口高级技巧

详解滑动窗口算法的通用模板与四类变体:固定窗口、可变窗口、多指针窗口与双窗口,覆盖最小覆盖子串、字符串排列、找到所有字母异位词等经典题目。

滑动窗口万能模板

滑动窗口是处理「子数组/子串」问题的利器。掌握一个通用模板,可以解决一类问题。

一、核心思想

窗口:数组/字符串中的一个连续区间 [left, right]

初始化 left = right = 0
while right < n:
    扩大窗口:加入 right 位置元素
    while 窗口满足某个条件:
        收缩窗口:移除 left 位置元素,left++
    更新结果
    right++

二、万能模板

def sliding_window(s):
    window = {}  # 记录窗口内元素
    left = 0
    result = ...
    for right in range(len(s)):
        # 扩大窗口:右指针右移,加入元素
        char_right = s[right]
        window[char_right] = window.get(char_right, 0) + 1

        # 收缩窗口:当窗口不满足条件时,左指针右移
        while 窗口不满足条件:
            char_left = s[left]
            window[char_left] -= 1
            if window[char_left] == 0:
                del window[char_left]
            left += 1

        # 更新结果(此时窗口满足条件)
        result = max/min(result, right - left + 1)

    return result

三、四类变体

1. 固定窗口大小

# 子数组最大平均数 I(LeetCode 643)
def find_max_average(nums, k):
    window_sum = sum(nums[:k])
    max_sum = window_sum
    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]
        max_sum = max(max_sum, window_sum)
    return max_sum / k

2. 可变窗口 — 找最小

# 最小覆盖子串(LeetCode 76)
from collections import Counter

def min_window(s, t):
    need = Counter(t)
    window = {}
    left = valid = 0
    start, length = 0, float('inf')

    for right in range(len(s)):
        c = s[right]
        if c in need:
            window[c] = window.get(c, 0) + 1
            if window[c] == need[c]:
                valid += 1

        while valid == len(need):
            if right - left + 1 < length:
                start, length = left, right - left + 1
            d = s[left]
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1
            left += 1

    return "" if length == float('inf') else s[start:start + length]

3. 可变窗口 — 找最大

# 无重复字符的最长子串(LeetCode 3)
def length_of_longest_substring(s):
    char_set = set()
    left = result = 0
    for right in range(len(s)):
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        char_set.add(s[right])
        result = max(result, right - left + 1)
    return result

4. 双窗口(需要维护两个条件)

# 替换后的最长重复字符(LeetCode 424)
def character_replacement(s, k):
    count = {}
    left = max_freq = result = 0
    for right in range(len(s)):
        count[s[right]] = count.get(s[right], 0) + 1
        max_freq = max(max_freq, count[s[right]])
        # 窗口大小 - 最多字符数 = 需要替换的字符数
        if (right - left + 1) - max_freq > k:
            count[s[left]] -= 1
            left += 1
        result = max(result, right - left + 1)
    return result

四、滑动窗口速查表

题号题目窗口类型关键条件
LeetCode 3无重复字符的最长子串最大可变无重复
LeetCode 76最小覆盖子串最小可变包含所有目标字符
LeetCode 209长度最小的子数组最小可变和 ≥ target
LeetCode 239滑动窗口最大值固定大小窗口最大值
LeetCode 438找到所有字母异位词固定大小异位词匹配
LeetCode 424替换后的最长重复字符最大可变替换 ≤ k 次
LeetCode 480滑动窗口中位数固定大小窗口中位数
LeetCode 567字符串的排列固定大小排列匹配

五、常见问题

Q: 什么时候用滑动窗口?

  • 连续子数组/子串问题
  • 条件可转化为窗口内的统计属性
  • 单调性:扩大窗口可能破坏条件,缩小可能恢复

Q: 为什么收缩用 while 不用 if?
因为左指针可能需要移动多步才能重新满足条件。

Q: 窗口内的统计用什么数据结构?

  • 字符频率:哈希表 / 数组(26/128/256)
  • 窗口最值:单调队列(LeetCode 239)
  • 窗口中位数:两个堆 / 有序集合

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页