滑动窗口的秘密——“滑动窗口”算法(Java版)
滑动窗口算法是一种高效的算法设计模式,尤其适用于处理数组或字符串等线性结构中的问题。它通过维护一个动态范围(或窗口)来缩小问题的规模,从而减少不必要的计算。在很多情况下,这种算法能够将时间复杂度从 O(n^2) 降到 O(n),显著提高效率。
滑动窗口的基本概念
滑动窗口的基本思想是使用两个指针来维护一个窗口,其中一个指针表示窗口的起始位置,另一个指针表示窗口的结束位置。通过不断地移动这两个指针,可以在保持一定条件的情况下遍历整个数据结构,以达到寻找解的目的。滑动窗口一般分为固定大小的窗口和动态大小的窗口,下面我们将分别讨论这两种情况。
固定大小的滑动窗口
在固定大小的滑动窗口中,我们通常需要维护一个窗口的大小,并在移动时更新窗口内的状态。例如,计算一个数组中每 K 个元素的最大值。
以下是一个 Java 示例,演示如何使用固定大小的滑动窗口找到数组中的每 K 个元素的最大值:
import java.util.Deque;
import java.util.LinkedList;
public class SlidingWindowMax {
public static int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) return new int[0];
int[] result = new int[nums.length - k + 1];
Deque<Integer> deque = new LinkedList<>(); // 双端队列
for (int i = 0; i < nums.length; i++) {
// 移除不在当前窗口内的元素
if (!deque.isEmpty() && deque.peek() < i - k + 1) {
deque.poll();
}
// 移除小于当前元素的所有元素
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// 添加当前元素的索引
deque.offer(i);
// 从第 k 个元素开始,记录最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peek()];
}
}
return result;
}
public static void main(String[] args) {
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = maxSlidingWindow(nums, k);
for (int max : result) {
System.out.print(max + " "); // 输出结果
}
// 输出: 3 3 5 5 6 7
}
}
动态大小的滑动窗口
动态大小的滑动窗口通常用来处理需要满足特定条件的子数组或子串。例如,寻找一个字符串中包含所有字符的最短子串。
以下是一个 Java 示例,演示如何使用动态大小的滑动窗口找到包含所有字符的最短子串:
import java.util.HashMap;
import java.util.Map;
public class MinWindowSubstring {
public static String minWindow(String s, String t) {
if (s.length() == 0 || t.length() == 0) return "";
Map<Character, Integer> charCount = new HashMap<>();
for (char c : t.toCharArray()) {
charCount.put(c, charCount.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0, minLength = Integer.MAX_VALUE, start = 0;
int required = charCount.size();
int formed = 0;
Map<Character, Integer> windowCounts = new HashMap<>();
while (right < s.length()) {
char c = s.charAt(right);
windowCounts.put(c, windowCounts.getOrDefault(c, 0) + 1);
if (charCount.containsKey(c) && windowCounts.get(c).intValue() == charCount.get(c).intValue()) {
formed++;
}
while (left <= right && formed == required) {
char leftChar = s.charAt(left);
if (right - left + 1 < minLength) {
minLength = right - left + 1;
start = left;
}
windowCounts.put(leftChar, windowCounts.get(leftChar) - 1);
if (charCount.containsKey(leftChar) && windowCounts.get(leftChar).intValue() < charCount.get(leftChar).intValue()) {
formed--;
}
left++;
}
right++;
}
return minLength == Integer.MAX_VALUE ? "" : s.substring(start, start + minLength);
}
public static void main(String[] args) {
String s = "ADOBECODEBANC";
String t = "ABC";
System.out.println(minWindow(s, t)); // 输出: "BANC"
}
}
总结
滑动窗口算法是一种简单而强大的技巧,可以解决许多线性数据结构的问题。无论是固定大小的窗户还是动态大小的窗户,理解其核心思想都是提升解题效率的关键。通过合适地设计窗口的移动方式,可以有效地降低算法的时间复杂度,提升代码的执行效率。在实际开发中,掌握滑动窗口算法能帮助我们更好地应对各种需求。