1. 滑动窗口算法核心原理剖析
滑动窗口算法是一种高效的数组/字符串处理技巧,它通过维护一个动态变化的窗口来避免不必要的重复计算。这个窗口通常由两个指针(左指针left和右指针right)定义,随着算法的执行,这两个指针会以特定的规则向右滑动。
核心思想:通过调整窗口边界来寻找满足特定条件的最优子区间,避免暴力枚举所有可能的子区间。
1.1 基本框架解析
滑动窗口算法的标准框架通常包含以下几个关键部分:
java复制int left = 0, right = 0;
while (right < s.length()) {
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}
这个框架的时间复杂度是O(N),因为每个元素最多被放入和取出窗口各一次。相比暴力解法的O(N²)有了显著提升。
1.2 三种典型应用场景
- 固定长度窗口:寻找特定长度的子串/子数组
- 可变长度窗口:寻找满足条件的最短/最长子串
- 计数型窗口:统计特定元素的出现频率
2. 滑动窗口实战:BISHI47问题详解
2.1 问题重述与分析
题目"交换到最大"的核心要求是:通过有限次数的相邻元素交换,使数组的某个子数组达到最大可能值。这实际上可以转化为:
- 确定一个窗口,窗口内的元素可以通过交换达到完全有序
- 找到能使窗口最大值最大的那个窗口
2.2 解法设计
python复制def max_after_swaps(nums, k):
max_val = max(nums)
max_window = 0
current_window = 0
for num in nums:
if num == max_val:
current_window += 1
max_window = max(max_window, current_window)
else:
current_window = 0
# 允许k次交换,实际可扩展窗口大小为min(k+max_window, len(nums))
return min(k + max_window, len(nums))
2.3 复杂度优化
通过维护滑动窗口,我们可以将时间复杂度从O(N²)降到O(N):
- 窗口扩展:当遇到最大值时扩大窗口
- 窗口收缩:当交换次数耗尽时收缩窗口
- 实时更新:始终保持当前最大窗口的追踪
3. 滑动窗口的四大经典问题
3.1 最小覆盖子串(LeetCode 76)
java复制public String minWindow(String s, String t) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0;
int valid = 0;
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right++);
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) valid++;
}
while (valid == need.size()) {
if (right - left < len) {
start = left;
len = right - left;
}
char d = s.charAt(left++);
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) valid--;
window.put(d, window.get(d) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
3.2 字符串排列(LeetCode 567)
python复制def checkInclusion(s1: str, s2: str) -> bool:
from collections import defaultdict
need = defaultdict(int)
window = defaultdict(int)
for c in s1: need[c] += 1
left, right = 0, 0
valid = 0
while right < len(s2):
c = s2[right]
right += 1
if c in need:
window[c] += 1
if window[c] == need[c]: valid += 1
while right - left >= len(s1):
if valid == len(need): return True
d = s2[left]
left += 1
if d in need:
if window[d] == need[d]: valid -= 1
window[d] -= 1
return False
3.3 找所有字母异位词(LeetCode 438)
java复制public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
if (s.length() < p.length()) return res;
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for (char c : p.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0;
int valid = 0;
while (right < s.length()) {
char c = s.charAt(right++);
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) valid++;
}
while (right - left >= p.length()) {
if (valid == need.size()) res.add(left);
char d = s.charAt(left++);
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) valid--;
window.put(d, window.get(d) - 1);
}
}
}
return res;
}
3.4 无重复字符的最长子串(LeetCode 3)
python复制def lengthOfLongestSubstring(s: str) -> int:
window = {}
left, right = 0, 0
res = 0
while right < len(s):
c = s[right]
right += 1
window[c] = window.get(c, 0) + 1
while window[c] > 1:
d = s[left]
left += 1
window[d] -= 1
res = max(res, right - left)
return res
4. 滑动窗口的优化技巧与常见问题
4.1 性能优化策略
- 哈希表优化:对于固定字符集(如小写字母),可以用数组代替哈希表
- 提前终止:当找到满足条件的解时可以提前返回
- 并行处理:对于大数据量可以考虑分段处理
4.2 常见错误与调试
| 错误类型 | 表现 | 解决方法 |
|---|---|---|
| 窗口收缩过度 | 错过有效解 | 检查收缩条件是否合理 |
| 更新时机错误 | 结果不准确 | 确认结果更新在收缩前还是收缩后 |
| 边界条件 | 数组越界 | 检查指针移动条件 |
4.3 调试技巧
- 可视化调试:打印窗口变化过程
python复制print(f"window: [{left}, {right}): {s[left:right]}")
- 单步跟踪:逐步执行观察变量变化
- 边界测试:特别关注空串、单字符等特殊情况
5. 滑动窗口的扩展应用
5.1 数值型数组问题
对于数值型数组,滑动窗口同样适用。例如求满足和≥target的最短子数组:
java复制public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0;
int minLen = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
5.2 多维滑动窗口
滑动窗口可以扩展到二维矩阵处理,例如图像处理中的区域统计:
python复制def max_pooling(matrix, k):
if not matrix: return []
m, n = len(matrix), len(matrix[0])
result = []
for i in range(0, m - k + 1):
row = []
for j in range(0, n - k + 1):
max_val = max(
matrix[x][y]
for x in range(i, i + k)
for y in range(j, j + k)
)
row.append(max_val)
result.append(row)
return result
5.3 时间序列分析
在时间序列数据中,滑动窗口可用于计算移动平均等指标:
python复制def moving_average(data, window_size):
window = []
result = []
for i, x in enumerate(data):
window.append(x)
if i >= window_size:
window.pop(0)
if len(window) == window_size:
result.append(sum(window) / window_size)
return result
在实际工程应用中,滑动窗口算法的关键在于:
- 明确定义窗口的扩展和收缩条件
- 高效维护窗口内的状态信息
- 正确处理边界情况和特殊输入
