滑动窗口算法

这是一篇关于滑动窗口的教程,涵盖固定窗口和可变窗口的基本概念、应用场景,并通过实际的 Java 示例代码帮助理解。


滑动窗口算法教程

滑动窗口(Sliding Window)是一种非常高效的算法技巧,常用于处理序列(如数组、字符串等)中的问题,特别是与子数组或子字符串相关的问题。通过维护一个窗口(通常是一个连续的元素范围),并在序列上滑动,滑动窗口技巧能够大大减少计算量,提升程序性能。

1. 滑动窗口概述

滑动窗口的核心思想是使用两个指针(通常称为左指针右指针)来表示当前窗口的范围。右指针负责扩展窗口,左指针负责收缩窗口,以满足题目要求的条件。通过左右指针的移动,窗口会在数据上滑动,从而高效地求解问题。

窗口的两种类型:
  • 固定窗口大小:窗口的大小是固定的,每次滑动时,窗口的范围不变,只是向右或向左移动。
  • 可变窗口大小:窗口的大小可以变化,通常根据某些条件来扩展或收缩窗口。

2. 固定窗口大小

固定窗口大小的滑动窗口指的是窗口的大小在整个过程中保持不变。每次滑动时,窗口只向前推进,新的元素进入窗口时,最旧的元素会被移除。

应用场景:

常用于求解固定大小的子数组或子字符串的相关问题,例如计算某个固定窗口大小的子数组的和、平均值、最大值等。

示例问题:
  • 问题:给定一个数组,求所有大小为 k 的子数组的和。
public class FixedSizeSlidingWindow {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6};
        int k = 3;
        int result = maxSum(arr, k);
        System.out.println("Maximum sum of any subarray of size " + k + " is: " + result);
    }

    public static int maxSum(int[] arr, int k) {
        // 初始化窗口的总和
        int windowSum = 0;
        int maxSum = 0;
        
        // 第一步:计算窗口初始值
        for (int i = 0; i < k; i++) {
            windowSum += arr[i];
        }
        maxSum = windowSum;

        // 第二步:滑动窗口
        for (int i = k; i < arr.length; i++) {
            // 滑动窗口:减去左边的元素,加上右边的元素
            windowSum += arr[i] - arr[i - k];
            maxSum = Math.max(maxSum, windowSum);
        }
        
        return maxSum;
    }
}
解释:
  • 初始时,我们计算数组前 k 个元素的和。
  • 然后,通过滑动窗口,逐步向右滑动,每次移出窗口左侧的元素,并加入新的右侧元素。
  • 在每次滑动时,更新窗口的和,并记录最大值。

3. 可变窗口大小

可变窗口大小的滑动窗口指的是窗口的大小是动态变化的,通常依据某些条件来扩展或收缩窗口。这种方法通常用于一些更复杂的情况,如寻找某种满足条件的子数组。

应用场景:

常用于解决一些求解最大、最小子数组的问题,如:

  • 寻找一个和小于某个值的最长子数组。
  • 寻找一个包含所有不同字符的最小子字符串。
示例问题:
  • 问题:给定一个字符串,找出其中没有重复字符的最长子字符串的长度。
import java.util.HashSet;

public class VariableSizeSlidingWindow {
    public static void main(String[] args) {
        String input = "abcabcbb";
        System.out.println("Length of the longest substring without repeating characters: " + lengthOfLongestSubstring(input));
    }

    public static int lengthOfLongestSubstring(String s) {
        HashSet<Character> set = new HashSet<>();
        int left = 0; // 左指针
        int maxLength = 0;

        // 右指针从左到右滑动
        for (int right = 0; right < s.length(); right++) {
            // 如果字符已经存在于集合中,收缩窗口,移动左指针
            while (set.contains(s.charAt(right))) {
                set.remove(s.charAt(left));
                left++;
            }
            // 将当前字符加入集合
            set.add(s.charAt(right));
            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
        }
        return maxLength;
    }
}
解释:
  • 左指针 left右指针 right 共同构成一个窗口。
  • 每次扩展 right 指针时,检查窗口内是否有重复的字符。如果有,就移动 left 指针来收缩窗口,直到窗口内没有重复字符为止。
  • right - left + 1 计算的是当前窗口内字符的个数,maxLength 用于记录遇到的最大长度。

4. 滑动窗口的时间复杂度

  • 固定窗口:每个元素最多只会被加入和移出一次,所以时间复杂度是 O(n),其中 n 是数组的长度。
  • 可变窗口:左右指针各自最多遍历一次整个数组,时间复杂度也是 O(n)。

因此,滑动窗口技术能够将一些传统的暴力算法的时间复杂度从 O(n^2) 降低到 O(n),大大提升算法效率。


5. 总结

滑动窗口是一种高效的算法技巧,适用于处理涉及子数组或子字符串的问题。通过维护一个动态或固定大小的窗口,滑动窗口能够高效地解决问题,减少不必要的计算。

  • 固定窗口:适用于子数组大小固定的问题,例如求和、求最大最小值。
  • 可变窗口:适用于更灵活的问题,例如查找满足条件的最长/最短子数组。

滑动窗口的关键是利用两个指针来表示窗口的起始和结束位置,逐步调整窗口的大小,快速求解问题。


通过这些示例和解释,希望你对滑动窗口算法有了更清晰的理解,并能够在实际编程中灵活应用它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋发元

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值