1. 滑动窗口算法概述
滑动窗口(Sliding Window)是解决字符串和数组子区间问题的高效算法范式。它通过维护一个动态变化的窗口来避免暴力解法中的重复计算,将时间复杂度从O(n²)优化到O(n)。在处理"找子串"这类问题时尤为有效,比如LeetCode第76题最小覆盖子串、第438题找到字符串中所有字母异位词等经典题目。
窗口滑动本质上是通过左右指针(left/right)的移动来调整考察区间。右指针负责扩展窗口,左指针负责收缩窗口,整个过程就像显微镜的载物台在样本上滑动观察。与暴力枚举所有子串相比,滑动窗口避免了重复扫描已检查区域,这是其性能优势的关键。
关键理解:滑动窗口的核心思想是"用空间换时间"——通过哈希表等数据结构记录窗口状态,使得每次窗口移动时只需常数时间更新状态,而非重新计算整个窗口。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 滑动窗口解决子串问题的通用框架
2.1 基础代码模板
python复制def sliding_window(s: str, t: str) -> ...:
left = right = 0
window = {} # 记录窗口内字符频次
needs = {} # 记录目标字符频次
for c in t: needs[c] = needs.get(c, 0) + 1
while right < len(s):
# 右移窗口
char = s[right]
window[char] = window.get(char, 0) + 1
right += 1
# 判断左侧窗口是否要收缩
while (window needs shrink condition):
# 更新结果(根据具体问题)
...
# 左移窗口
left_char = s[left]
window[left_char] -= 1
if window[left_char] == 0:
del window[left_char]
left += 1
return ...
2.2 关键参数解析
| 参数 | 作用 | 典型初始化值 |
|---|---|---|
| left/right | 窗口左右边界(左闭右开区间) | 0 |
| window | 当前窗口内字符的统计字典 | {} |
| needs | 目标子串的字符需求字典 | 根据t构建 |
| valid | 满足条件的字符计数(可选) | 0 |
2.3 窗口收缩条件
不同问题的收缩条件各异,常见的有:
- 窗口已包含所有目标字符(最小覆盖子串)
- 窗口内字符超出允许的最大种类数(最多包含K个不同字符的子串)
- 窗口大小等于目标串长度(字母异位词)
3. 典型子串问题实战解析
3.1 最小覆盖子串(LeetCode 76)
给定字符串S和T,在S中找到包含T所有字符的最短子串。
python复制def minWindow(s: str, t: str) -> str:
from collections import defaultdict
need = defaultdict(int)
for c in t: need[c] += 1
needCnt = len(t)
left = 0
res = (0, float('inf'))
for right, c in enumerate(s):
if need[c] > 0:
needCnt -= 1
need[c] -= 1
# 窗口已包含所有所需字符
if needCnt == 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
needCnt += 1
left += 1
return s[res[0]:res[1]+1] if res[1] < len(s) else ""
优化点:使用needCnt计数器避免每次遍历need字典检查是否满足条件,将O(|Σ|)的判断优化为O(1)。
3.2 字母异位词(LeetCode 438)
找到字符串中所有字母异位词的起始索引。
python复制def findAnagrams(s: str, p: str) -> List[int]:
from collections import defaultdict
need = defaultdict(int)
for c in p: need[c] += 1
needCnt = len(p)
res = []
left = 0
for right, c in enumerate(s):
if need[c] > 0:
needCnt -= 1
need[c] -= 1
# 窗口大小等于p长度时检查
if right - left + 1 == len(p):
if needCnt == 0:
res.append(left)
# 移动左边界
if need[s[left]] >= 0:
needCnt += 1
need[s[left]] += 1
left += 1
return res
关键技巧:固定窗口大小为len(p),每次移动时整体右移,类似卷积核滑动。
3.3 最多包含两个不同字符的最长子串
(LeetCode 159扩展题)
python复制def lengthOfLongestSubstringTwoDistinct(s: str) -> int:
from collections import defaultdict
count = defaultdict(int)
left = max_len = 0
for right, c in enumerate(s):
count[c] += 1
# 当字符种类超过2时收缩窗口
while len(count) > 2:
left_char = s[left]
count[left_char] -= 1
if count[left_char] == 0:
del count[left_char]
left += 1
max_len = max(max_len, right - left + 1)
return max_len
变种扩展:将判断条件改为len(count) > K即可解决最多包含K个不同字符的问题。
4. 性能优化与边界处理
4.1 哈希表选择对比
| 数据结构 | 适用场景 | 时间复杂度 |
|---|---|---|
| 标准字典 | 字符范围明确且有限(如字母) | O(1) |
| defaultdict | 需要处理键不存在的情况 | O(1) |
| Counter | 需要快速统计频次 | O(n)构建 |
| 数组(ASCII码) | 字符集为ASCII时更高效 | O(1) |
建议:当字符范围明确为小写字母时,使用[0]*26的数组比哈希表更快。
4.2 常见陷阱与解决方案
-
右指针移动时机:
- 错误:在更新窗口状态前移动right
- 正确:先处理当前right指向的字符,再移动指针
-
结果更新位置:
- 应在收缩窗口后立即更新,而非在循环外
-
空输入处理:
python复制if not s or not t or len(t) > len(s): return "" -
重复字符处理:
- 使用频次统计而非集合来判断包含关系
5. 滑动窗口的工程应用扩展
5.1 实时流处理中的窗口统计
在日志分析、网络监控等场景,滑动窗口可用于:
- 计算最近1分钟的请求成功率
- 检测突发流量(单位时间内的请求量突增)
- 实现限流算法(如滑动窗口限流)
python复制class SlidingWindowCounter:
def __init__(self, window_size=60):
self.window = deque()
self.window_size = window_size
def add_event(self, timestamp):
# 移除过期事件
while self.window and timestamp - self.window[0] > self.window_size:
self.window.popleft()
self.window.append(timestamp)
def get_count(self):
return len(self.window)
5.2 时间序列数据分析
处理股票价格、传感器数据时:
- 计算移动平均线
- 检测异常波动
- 寻找特定模式片段
python复制def moving_average(data, window_size):
window = deque(maxlen=window_size)
result = []
for item in data:
window.append(item)
if len(window) == window_size:
result.append(sum(window)/window_size)
return result
5.3 文本编辑器中的查找优化
现代IDE的代码搜索功能常采用滑动窗口思想:
- 快速定位相似代码片段
- 模糊匹配标识符
- 批量替换时的范围确认
经验之谈:在实现自己的滑动窗口算法时,建议先在纸上画出窗口移动过程,标注每个步骤的状态变化。这能帮助理清边界条件和状态更新逻辑,避免陷入死循环或漏判情况。
