滑动窗口算法精讲

29 阅读4分钟

滑动窗口核心思想

滑动窗口是一种双指针技巧,主要用于解决数组/字符串的连续子区间问题。通过维护一个可滑动的窗口,在O(n)时间复杂度内解决问题。

基本框架:

java

int left = 0, right = 0;
while (right < n) {
    // 1. 进窗口
    // 2. 判断条件
    while (条件满足) {
        // 3. 更新结果
        // 4. 出窗口
        left++;
    }
    right++;
}

题目详解

1. 长度最小的子数组

问题:找到和≥target的最短连续子数组

java

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0;
        int n = nums.length;
        int sum = 0;
        int minLen = Integer.MAX_VALUE;
        
        for (; right < n; right++) {
            sum += nums[right];  // 进窗口
            
            while (sum >= target) {  // 判断条件
                minLen = Math.min(minLen, right - left + 1);  // 更新结果
                sum -= nums[left];  // 出窗口
                left++;
            }
        }
        
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
}

关键点

  • 右指针扩大窗口,左指针缩小窗口
  • 在满足条件时立即更新结果

2. 无重复字符的最长子串

问题:找到不含重复字符的最长子串

java

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] hash = new int[128];  // ASCII码范围
        char[] arr = s.toCharArray();
        int left = 0, right = 0;
        int maxLen = 0;
        int n = arr.length;
        
        for (; right < n; right++) {
            hash[arr[right]]++;  // 进窗口
            
            while (hash[arr[right]] > 1) {  // 出现重复
                hash[arr[left]]--;  // 出窗口
                left++;
            }
            
            maxLen = Math.max(maxLen, right - left + 1);  // 更新结果
        }
        
        return maxLen;
    }
}

优化:使用数组代替HashMap,性能更好


3. 最大连续1的个数

问题:最多翻转k个0,找到最长连续1

java

class Solution {
    public int longestOnes(int[] nums, int k) {
        int left = 0, right = 0;
        int zeroCount = 0;
        int maxLen = 0;
        int n = nums.length;
        
        for (; right < n; right++) {
            if (nums[right] == 0) {
                zeroCount++;  // 进窗口
            }
            
            while (zeroCount > k) {  // 超过翻转限制
                if (nums[left] == 0) {
                    zeroCount--;  // 出窗口
                }
                left++;
            }
            
            maxLen = Math.max(maxLen, right - left + 1);  // 更新结果
        }
        
        return maxLen;
    }
}

技巧:把0的个数当作"资源",不超过k即可


4. 将x减到0的最小操作数

问题:从两端移除元素使和等于x

java

class Solution {
    public int minOperations(int[] nums, int x) {
        int total = 0;
        for (int num : nums) total += num;
        
        int target = total - x;  // 中间连续子数组的和
        if (target < 0) return -1;
        
        int left = 0, right = 0;
        int currentSum = 0;
        int maxLen = -1;
        int n = nums.length;
        
        for (; right < n; right++) {
            currentSum += nums[right];  // 进窗口
            
            while (currentSum > target && left <= right) {
                currentSum -= nums[left];  // 出窗口
                left++;
            }
            
            if (currentSum == target) {
                maxLen = Math.max(maxLen, right - left + 1);  // 更新结果
            }
        }
        
        return maxLen == -1 ? -1 : n - maxLen;
    }
}

思路转换:移除两边 → 保留中间连续子数组


5. 水果成篮

问题:最多收集两种水果,求最大数量

java

class Solution {
    public int totalFruit(int[] fruits) {
        int[] hash = new int[100001];  // 题目数据范围
        int left = 0, right = 0;
        int typeCount = 0;
        int maxLen = 0;
        int n = fruits.length;
        
        for (; right < n; right++) {
            if (hash[fruits[right]] == 0) {
                typeCount++;  // 新水果类型
            }
            hash[fruits[right]]++;  // 进窗口
            
            while (typeCount > 2) {  // 超过两种水果
                hash[fruits[left]]--;  // 出窗口
                if (hash[fruits[left]] == 0) {
                    typeCount--;
                }
                left++;
            }
            
            maxLen = Math.max(maxLen, right - left + 1);  // 更新结果
        }
        
        return maxLen;
    }
}

关键:维护水果类型计数


6. 找到字符串中所有字母的异位词

问题:在s中找到p的所有异位词起始位置

java

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<>();
        int[] target = new int[26];
        int[] window = new int[26];
        
        // 统计目标字符串
        for (char c : p.toCharArray()) {
            target[c - 'a']++;
        }
        
        int left = 0, right = 0;
        int validCount = 0;
        int sLen = s.length(), pLen = p.length();
        
        for (; right < sLen; right++) {
            char in = s.charAt(right);
            window[in - 'a']++;  // 进窗口
            
            // 有效字符判断
            if (window[in - 'a'] <= target[in - 'a']) {
                validCount++;
            }
            
            // 窗口大小固定为p的长度
            if (right - left + 1 > pLen) {
                char out = s.charAt(left);
                if (window[out - 'a'] <= target[out - 'a']) {
                    validCount--;
                }
                window[out - 'a']--;  // 出窗口
                left++;
            }
            
            // 找到异位词
            if (validCount == pLen) {
                result.add(left);
            }
        }
        
        return result;
    }
}

技巧:固定窗口大小,统计有效字符数


7. 最小覆盖子串

问题:在s中找到包含t所有字符的最小子串

java

class Solution {
    public String minWindow(String s, String t) {
        int[] target = new int[128];
        int[] window = new int[128];
        
        // 统计目标字符
        for (char c : t.toCharArray()) {
            target[c]++;
        }
        
        int left = 0, right = 0;
        int validCount = 0;
        int minLen = Integer.MAX_VALUE;
        int start = 0;
        int sLen = s.length(), tLen = t.length();
        
        for (; right < sLen; right++) {
            char in = s.charAt(right);
            window[in]++;  // 进窗口
            
            if (window[in] <= target[in]) {
                validCount++;  // 有效字符
            }
            
            // 找到覆盖子串,尝试收缩窗口
            while (validCount == tLen) {
                // 更新最小覆盖子串
                if (right - left + 1 < minLen) {
                    minLen = right - left + 1;
                    start = left;
                }
                
                char out = s.charAt(left);
                if (window[out] <= target[out]) {
                    validCount--;  // 减少有效字符
                }
                window[out]--;  // 出窗口
                left++;
            }
        }
        
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }
}

关键:维护有效字符计数,找到覆盖后立即收缩


滑动窗口总结

适用场景

  • 连续子数组/子字符串问题
  • 需要维护某种"条件"的区间
  • 时间复杂度要求O(n)

解题步骤

  1. 确定窗口维护的"条件"(和、字符种类、有效计数等)
  2. 右指针扩大窗口,更新条件
  3. 当条件满足时,更新结果并收缩窗口
  4. 左指针收缩窗口,恢复条件

复杂度分析

  • 时间复杂度:O(n),每个元素进出窗口各一次
  • 空间复杂度:O(字符集大小)

常见变体

  • 固定大小窗口(如异位词问题)
  • 可变大小窗口(如最小子数组)
  • 多条件窗口(如水果成篮)

掌握这些模板,就能解决大多数滑动窗口问题!