滑动窗口算法入门:原理、应用与LeetCode实战
2026/8/3 6:23:15 网站建设 项目流程

1. 项目概述

"跟着灵神学算法"系列是一个面向算法初学者的系统性学习项目,Day1作为入门篇章,重点聚焦滑动窗口这一基础但强大的算法技巧。作为算法竞赛和面试中的常客,滑动窗口以其O(n)的时间复杂度优势,成为处理子串、子数组问题的首选方案。

我在实际刷题和算法教学中发现,90%的初学者在首次接触滑动窗口时,都会陷入"暴力解法优化不来"的困境。这个系列将采用"问题驱动+可视化演示"的方式,带你从LeetCode真题入手,逐步掌握滑动窗口的三大应用场景和六种变形解法。

2. 滑动窗口核心原理

2.1 算法思想本质

滑动窗口本质上是通过维护一个动态变化的区间,避免重复计算来提升效率。就像用望远镜观察风景时,我们不会每次移动都重新调整焦距,而是保持镜筒平稳滑动。

以经典的"无重复字符的最长子串"问题为例:

  • 暴力解法需要O(n²)时间检查所有子串
  • 滑动窗口通过左右指针维护当前窗口,只需O(n)即可完成扫描
def lengthOfLongestSubstring(s: str) -> int: char_index = {} # 存储字符最后出现位置 left = max_len = 0 for right, char in enumerate(s): if char in char_index and char_index[char] >= left: left = char_index[char] + 1 char_index[char] = right max_len = max(max_len, right - left + 1) return max_len

2.2 两种基本类型

  1. 固定长度窗口

    • 窗口大小保持不变
    • 典型问题:字符串的排列组合检查
    • 实现要点:右指针每次移动时,同步移动左指针
  2. 可变长度窗口

    • 窗口根据条件动态扩展/收缩
    • 典型问题:满足条件的最短子数组
    • 实现要点:需要维护窗口状态变量

关键技巧:使用哈希表记录窗口内元素频次时,要注意处理计数为0的情况,避免错误判断

3. 实战应用解析

3.1 字符串类问题

例题:最小覆盖子串(LeetCode 76)

给定字符串S和T,在S中找到包含T所有字符的最短子串。这个问题的难点在于:

  • 需要处理字符重复出现的情况
  • 窗口收缩条件复杂
def minWindow(s: str, t: str) -> str: from collections import defaultdict need = defaultdict(int) for c in t: need[c] += 1 need_cnt = len(t) left = 0 res = (0, float('inf')) for right, c in enumerate(s): if need[c] > 0: need_cnt -= 1 need[c] -= 1 if need_cnt == 0: # 满足条件时收缩窗口 while True: c = s[left] if need[c] == 0: break need[c] += 1 left += 1 if right - left < res[1] - res[0]: res = (left, right) need[s[left]] += 1 need_cnt += 1 left += 1 return s[res[0]:res[1]+1] if res[1] < float('inf') else ""

3.2 数组类问题

例题:和至少为K的最短子数组(LeetCode 862)

这道题需要结合前缀和与单调队列实现滑动窗口:

  1. 计算前缀和数组pre_sum
  2. 维护单调递增队列
  3. 遍历时比较队列首尾差值
def shortestSubarray(nums: List[int], k: int) -> int: from collections import deque n = len(nums) pre_sum = [0] * (n + 1) for i in range(n): pre_sum[i+1] = pre_sum[i] + nums[i] q = deque() res = float('inf') for i in range(n+1): while q and pre_sum[i] - pre_sum[q[0]] >= k: res = min(res, i - q.popleft()) while q and pre_sum[i] <= pre_sum[q[-1]]: q.pop() q.append(i) return res if res != float('inf') else -1

4. 常见问题与优化技巧

4.1 边界条件处理

  1. 空输入处理

    • 检查输入字符串/数组是否为空
    • 特殊处理长度为1的情况
  2. 无效窗口判断

    • 当右指针到达末尾但窗口不满足条件时
    • 使用哨兵值简化判断逻辑

4.2 性能优化策略

  1. 哈希表预分配

    # 对于已知字符范围的情况(如仅小写字母) count = [0] * 26
  2. 提前终止

    • 当找到理论最小窗口时立即返回
    • 在遍历中加入early break条件
  3. 双指针同步移动

    • 某些情况下左右指针可以同步前进
    • 减少不必要的窗口收缩操作

4.3 调试技巧

  1. 可视化打印

    print(f"窗口[{left}:{right}]: {s[left:right+1]}")
  2. 状态检查

    assert sum(count.values()) == right - left + 1
  3. 测试用例设计

    • 包含重复字符的字符串
    • 全相同元素的极端情况
    • 目标字符串包含不存在字符的情况

5. 进阶训练建议

掌握基础滑动窗口后,建议按以下顺序进阶:

  1. 先刷完LeetCode滑动窗口标签下的所有简单题
  2. 然后挑战中等难度经典题:
    • 340.至多包含K个不同字符的最长子串
    • 424.替换后的最长重复字符
    • 992.K个不同整数的子数组
  3. 最后尝试hard题目:
    • 76.最小覆盖子串(上文已解析)
    • 239.滑动窗口最大值(需结合单调队列)

我个人的训练心得是:每天坚持3道滑动窗口变种题,连续两周后会发现这类问题都有固定套路。建议准备错题本记录以下信息:

  • 初始错误解法
  • 卡壳点分析
  • 最终AC代码
  • 时间/空间复杂度分析

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询