1. 滑动窗口算法基础概念
滑动窗口算法(Sliding Window)是一种在数组或字符串处理中常用的优化技术。它通过维护一个大小可变或固定的"窗口",在数据序列上滑动这个窗口来避免重复计算,从而降低时间复杂度。我第一次接触这个算法是在处理大规模日志分析时,当时需要从TB级数据中快速找出异常时间段,滑动窗口帮我将时间复杂度从O(n²)优化到了O(n)。
窗口通常由两个指针表示:
- 左指针(left):标记窗口的起始位置
- 右指针(right):标记窗口的结束位置
这种算法的精妙之处在于,它能够利用问题的特定性质(如单调性、子串特征等)来避免不必要的计算。比如在解决"无重复字符的最长子串"问题时,当遇到重复字符,我们可以直接移动左指针到重复字符的下一个位置,而不是重新开始计算。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 滑动窗口的两种典型模式
2.1 固定大小窗口
固定窗口大小的滑动窗口通常用于解决需要计算连续子数组/子串的统计量问题。比如计算大小为k的子数组的平均值:
java复制public double[] findAverages(int[] arr, int k) {
double[] result = new double[arr.length - k + 1];
double windowSum = 0;
int windowStart = 0;
for (int windowEnd = 0; windowEnd < arr.length; windowEnd++) {
windowSum += arr[windowEnd]; // 累加右侧新元素
if (windowEnd >= k - 1) { // 窗口达到指定大小
result[windowStart] = windowSum / k;
windowSum -= arr[windowStart]; // 减去左侧即将移出的元素
windowStart++; // 移动窗口左边界
}
}
return result;
}
关键点:窗口大小固定时,右指针每次移动都要检查窗口是否达到要求大小,同时要记得在移动左指针时从和中减去左侧元素值。
2.2 可变大小窗口
可变窗口更常见于寻找满足特定条件的最优子数组/子串问题。典型问题如"最小覆盖子串":
java复制public String minWindow(String s, String t) {
Map<Character, Integer> need = 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;
Map<Character, Integer> window = new HashMap<>();
while (right < s.length()) {
char c = s.charAt(right);
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);
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);
}
实现要点:使用valid变量记录当前窗口中满足条件的字符数量,当valid等于需要的字符种类数时,开始收缩左边界寻找最小窗口。
3. 滑动窗口算法的Java实现细节
3.1 边界条件处理
在实际编码中,边界条件的处理往往是bug的高发区。以下是一些常见情况的处理方式:
- 空输入处理:
java复制if (nums == null || nums.length == 0) {
return 0; // 或其他默认值
}
- 窗口大小大于数组长度:
java复制if (k > nums.length) {
// 根据问题要求返回:
// - 可能返回整个数组的统计量
// - 可能返回空结果
// - 可能抛出异常
}
- 右指针到达末尾时的处理:
java复制while (right < s.length()) {
// 常规处理...
