1. 滑动窗口算法概述
滑动窗口(Sliding Window)是解决字符串和数组子区间问题的高效算法范式。它通过维护一个动态变化的窗口来避免暴力枚举,将时间复杂度从O(n²)优化到O(n)。在处理"找子串"这类问题时尤为有效,比如:
- 寻找无重复字符的最长子串
- 包含所有字符的最短子串
- 字符异位词(字母相同但顺序不同的词)检测
- 满足特定条件的最长/最短连续子数组
核心思想:通过左右指针划定窗口边界,右指针探索新元素,左指针收缩无效区域,像推拉窗一样动态调整观察范围。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 滑动窗口解决子串问题的通用框架
2.1 基础双指针实现
python复制def sliding_window(s: str):
left = 0
window = {} # 存储窗口内字符状态
res = 0 # 存储结果
for right in range(len(s)):
# 右指针扩展窗口
char = s[right]
window[char] = window.get(char, 0) + 1
# 左指针收缩窗口的条件
while window_needs_shrink(window):
# 更新结果
res = update_result(res, right-left+1)
# 左移指针并更新窗口状态
left_char = s[left]
window[left_char] -= 1
if window[left_char] == 0:
del window[left_char]
left += 1
return res
2.2 关键参数解析
| 参数 | 作用 | 典型初始化值 |
|---|---|---|
| left | 窗口左边界 | 0 |
| right | 窗口右边界 | 0 |
| window | 记录窗口内字符频次的哈希表 | {} |
| res | 存储满足条件的最优解 | 0或float('inf') |
3. 典型子串问题实战
3.1 无重复字符的最长子串
问题描述:给定字符串s,找出不含有重复字符的最长子串长度。
解决方案:
python复制def lengthOfLongestSubstring(s: str) -> int:
left = 0
max_len = 0
char_index = {} # 记录字符最后出现的位置
for right in range(len(s)):
if s[right] in char_index:
# 遇到重复字符时,左指针跳到该字符上次出现位置的下一位
left = max(left, char_index[s[right]] + 1)
char_index[s[right]] = right
max_len = max(max_len, right - left + 1)
return max_len
复杂度分析:
- 时间复杂度:O(n),每个字符最多被访问两次(右指针扩展和左指针收缩)
- 空间复杂度:O(min(m,n)),m为字符集大小
3.2 最小覆盖子串
问题描述:给定字符串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
left = 0
valid = 0 # 满足条件的字符数
window = defaultdict(int)
min_len = float('inf')
res = ""
for right in range(len(s)):
c = s[right]
if c in need:
window[c] += 1
if window[c] == need[c]:
valid += 1
while valid == len(need):
# 更新最小窗口
if right - left + 1 < min_len:
min_len = right - left + 1
res = s[left:right+1]
# 收缩窗口
d = s[left]
if d in need:
if window[d] == need[d]:
valid -= 1
window[d] -= 1
left += 1
return res
优化技巧:
- 使用defaultdict避免键不存在时的异常
- valid计数器避免频繁遍历need字典
- 提前存储s长度减少重复计算
4. 异位词相关问题
4.1 字符串异位词检测
问题变体:判断s2是否包含s1的排列(即s1的异位词)
python复制def checkInclusion(s1: str, s2: str) -> bool:
from collections import defaultdict
need = defaultdict(int)
for c in s1:
need[c] += 1
left = 0
valid = 0
window = defaultdict(int)
for right in range(len(s2)):
c = s2[right]
if c in need:
window[c] += 1
if window[c] == need[c]:
valid += 1
# 固定窗口大小为s1长度
while right - left + 1 >= len(s1):
if valid == len(need):
return True
d = s2[left]
if d in need:
if window[d] == need[d]:
valid -= 1
window[d] -= 1
left += 1
return False
4.2 所有异位词的起始索引
问题扩展:找到s中所有是p的异位词的子串的起始索引
python复制def findAnagrams(s: str, p: str) -> List[int]:
from collections import defaultdict
res = []
need = defaultdict(int)
for c in p:
need[c] += 1
left = 0
valid = 0
window = defaultdict(int)
for right in range(len(s)):
c = s[right]
if c in need:
window[c] += 1
if window[c] == need[c]:
valid += 1
while right - left + 1 >= len(p):
if valid == len(need):
res.append(left)
d = s[left]
if d in need:
if window[d] == need[d]:
valid -= 1
window[d] -= 1
left += 1
return res
5. 高级变种与优化技巧
5.1 最多包含K个不同字符的最长子串
问题描述:给定字符串s和整数k,找出最多包含k个不同字符的最长子串长度。
python复制def lengthOfLongestSubstringKDistinct(s: str, k: int) -> int:
from collections import defaultdict
left = 0
max_len = 0
char_count = defaultdict(int)
for right in range(len(s)):
char_count[s[right]] += 1
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
max_len = max(max_len, right - left + 1)
return max_len
5.2 使用deque优化窗口最值
当需要频繁获取窗口最大值/最小值时,可以用双端队列优化:
python复制from collections import deque
def maxSlidingWindow(nums: List[int], k: int) -> List[int]:
q = deque()
res = []
for i, num in enumerate(nums):
# 维护单调递减队列
while q and nums[q[-1]] <= num:
q.pop()
q.append(i)
# 移除超出窗口范围的索引
if q[0] == i - k:
q.popleft()
# 当窗口形成时记录最大值
if i >= k - 1:
res.append(nums[q[0]])
return res
6. 常见问题与调试技巧
6.1 窗口收缩条件不明确
症状:结果总是不正确或遗漏某些情况
解决方案:
- 明确记录窗口状态的变量(如valid计数器)
- 在纸上模拟简单测试用例的执行流程
- 添加调试打印语句观察窗口变化
6.2 边界条件处理
典型错误:
- 空字符串输入
- 所有字符都相同的情况
- 窗口大小等于字符串长度
防御性编程:
python复制if not s: return 0 # 处理空输入
if len(set(s)) == 1: return 1 # 全相同字符
6.3 性能优化检查表
- 用字典代替Counter减少开销
- 避免在循环内创建新字典
- 使用早期终止条件(如找到解立即返回)
- 对于固定大小窗口,直接用if替代while
7. 不同语言实现要点
7.1 Java实现注意事项
java复制// 使用int[128]替代HashMap处理ASCII字符
int[] window = new int[128];
// 字符转ASCII索引直接相减
window[s.charAt(right) - 'a']++;
// 判断条件优化
if (Arrays.equals(window, need)) {
// 找到解
}
7.2 C++实现技巧
cpp复制// 使用unordered_map时注意默认值
unordered_map<char, int> window;
// 右扩展
window[s[right]]++;
// 左收缩
if (--window[s[left]] == 0) {
window.erase(s[left]);
}
7.3 JavaScript特殊处理
javascript复制// 处理Unicode字符
const codePoint = s.charCodeAt(right);
// 使用对象模拟字典
const window = {};
// 检查属性存在
if (window[codePoint] > 0) {
// 存在重复
}
掌握滑动窗口算法的关键在于理解窗口扩张与收缩的触发条件,并通过大量练习培养对窗口状态维护的直觉。建议从基础问题入手,逐步过渡到复杂变种,同时注意不同语言实现的性能差异。
