Java滑动窗口算法题目练习

下面给你一套系统化的 Java 滑动窗口算法练习计划,从基础到进阶,包含经典题型、模板代码、思路分析和 Java 实现。

滑动窗口主要分为两大类:

  • 固定窗口大小(Fixed Size Sliding Window)
  • 可变窗口大小(Variable Size / Dynamic Sliding Window)

推荐练习顺序(由易到难)

难度题号 & 标题窗口类型核心技巧推荐指数
简单643. Maximum Average Subarray I固定前缀和 / 滑动求和★★★★
简单209. Minimum Size Subarray Sum可变最小长度 ≥ target★★★★★
中等3. Longest Substring Without Repeating Characters可变HashSet / HashMap 记录字符★★★★★
中等438. Find All Anagrams in a String固定字符频率计数(滑动匹配)★★★★
中等567. Permutation in String固定滑动窗口 + 频率数组★★★★
中等76. Minimum Window Substring可变最小覆盖子串(need & window)★★★★★
中等1004. Max Consecutive Ones III可变最多允许 k 个 0★★★★
困难239. Sliding Window Maximum固定单调队列(deque)★★★★★
困难424. Longest Repeating Character Replacement可变最多替换 k 个字符★★★★
困难295. Find Median from Data Stream (进阶变形)动态中位数两个堆(非纯滑动,但相关)★★★

滑动窗口通用模板(Java)

1. 固定窗口大小模板

class Solution {
    public int fixedWindow(int[] nums, int k) {
        int n = nums.length;
        if (n < k) return 0; // 或其他无效值

        int left = 0, right = 0;
        int sum = 0;           // 或其他状态变量(如计数器)
        int maxSum = Integer.MIN_VALUE;

        while (right < n) {
            // 加入右边界元素
            sum += nums[right];
            right++;

            // 当窗口达到固定大小 k
            if (right - left == k) {
                // 更新答案
                maxSum = Math.max(maxSum, sum);

                // 移除左边界元素,窗口向前滑动
                sum -= nums[left];
                left++;
            }
        }
        return maxSum;
    }
}

2. 可变窗口大小模板(最常用)

class Solution {
    public int variableWindow(int[] nums, int target) {
        int left = 0;
        int sum = 0;           // 当前窗口和 / 计数等
        int minLen = Integer.MAX_VALUE;

        for (int right = 0; right < nums.length; right++) {
            // 加入右边界
            sum += nums[right];

            // 窗口收缩条件(while 非常关键)
            while (sum >= target && left <= right) {
                // 更新答案
                minLen = Math.min(minLen, right - left + 1);

                // 移除左边界
                sum -= nums[left];
                left++;
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
}

经典题目 Java 代码 + 思路精讲

1. 209. 长度最小的子数组(可变窗口,最经典入门题)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        int left = 0;
        int sum = 0;
        int minLen = Integer.MAX_VALUE;

        for (int right = 0; right < n; right++) {
            sum += nums[right];

            while (sum >= target && left <= right) {
                minLen = Math.min(minLen, right - left + 1);
                sum -= nums[left++];
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
}

2. 3. 无重复字符的最长子串(可变窗口 + HashMap/数组)

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int left = 0;
        int maxLen = 0;
        int[] window = new int[128]; // ASCII 字符

        for (int right = 0; right < n; right++) {
            char c = s.charAt(right);
            window[c]++;

            while (window[c] > 1) {
                char d = s.charAt(left);
                window[d]--;
                left++;
            }

            maxLen = Math.max(maxLen, right - left + 1);
        }
        return maxLen;
    }
}

3. 76. 最小覆盖子串(最经典可变窗口高级题)

class Solution {
    public String minWindow(String s, String t) {
        if (s == null || t == null || s.length() < t.length()) return "";

        int[] need = new int[128];
        int[] window = new int[128];

        // 记录 t 中每个字符需要的次数
        for (char c : t.toCharArray()) {
            need[c]++;
        }

        int left = 0, right = 0;
        int valid = 0;          // 窗口中已满足条件的字符数
        int start = 0, len = Integer.MAX_VALUE;

        while (right < s.length()) {
            char c = s.charAt(right);
            right++;
            if (need[c] > 0) {
                window[c]++;
                if (window[c] == need[c]) valid++;
            }

            // 收缩窗口
            while (valid == t.length()) {
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }

                char d = s.charAt(left);
                left++;
                if (need[d] > 0) {
                    if (window[d] == need[d]) valid--;
                    window[d]--;
                }
            }
        }

        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

4. 239. 滑动窗口最大值(固定窗口 + 单调队列)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) return new int[0];

        Deque<Integer> deque = new ArrayDeque<>(); // 存索引,单调递减
        int[] result = new int[n - k + 1];

        for (int i = 0; i < n; i++) {
            // 移除窗口外的元素
            while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
                deque.pollFirst();
            }

            // 保持单调递减(队首是最大值)
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offerLast(i);

            // 窗口形成后记录最大值
            if (i >= k - 1) {
                result[i - k + 1] = nums[deque.peekFirst()];
            }
        }
        return result;
    }
}

练习建议

  1. 先掌握 固定窗口(643、1004)→ 熟悉窗口滑动的基本节奏
  2. 再练 可变窗口 + 计数(3、209、424、567、438)
  3. 挑战 最小覆盖子串(76)→ 这是可变窗口的集大成者
  4. 最后攻克 单调队列(239)→ 固定窗口的进阶玩法
  5. 每天 2–3 题,写完后对比官方题解的优化点

如果你想针对某一道题要详细的多种解法对比(HashMap vs 数组 vs BitSet 等)、边界case分析,或者想加几道面试高频变形题(如水果成篮、至多k种字符等),告诉我,我可以继续展开。祝你刷题顺利!

文章已创建 4631

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部