算法高频压轴题|滑动窗口最大值 + 最小覆盖子串,单调队列 + 滑动窗口双杀

目录

[239. 滑动窗口最大值](#239. 滑动窗口最大值)

题目描述

解题思路

满分代码(自定义单调队列)

核心亮点

[76. 最小覆盖子串(Hard)](#76. 最小覆盖子串(Hard))

题目描述

解题思路

满分代码(极简注释版)

核心关键点

刷题总结


这两道题是算法面试必考的滑动窗口体系核心题 :一道是单调队列 的经典应用(滑动窗口最大值),一道是滑动窗口 + 哈希表的 Hard 级天花板(最小覆盖子串)。我用最通俗的思路拆解 + 满分注释代码,新手也能直接看懂、上手就写!


239. 滑动窗口最大值

题目描述

给定一个数组 nums 和一个滑动窗口大小 k,从数组最左侧移动到最右侧,每次窗口移动一位,返回每个窗口内的最大值

解题思路

暴力解法会遍历所有窗口,时间复杂度 O(n∗k) 直接超时。最优解:自定义单调递减队列 **核心思想:

  1. 维护一个双端队列 ,保证队列从队头到队尾单调递减
  2. 队首永远是当前窗口的最大值
  3. 入队:新元素入队时,移除所有比它小的队尾元素(小元素不可能成为最大值,直接舍弃);
  4. 出队:窗口滑动时,若要移除的元素恰好是队首最大值,才将其出队;
  5. 每个窗口直接取队首元素,就是当前窗口最大值。

满分代码(自定义单调队列)

java

运行

复制代码
/**
 * 自定义单调递减队列
 * 核心保证:队首元素永远是当前窗口的最大值
 */
class MyDeque {
    Deque<Integer> deque = new LinkedList<>();

    // 出队操作:只有要移除的元素是队首最大值时,才弹出
    void poll(int val) {
        if (!deque.isEmpty() && val == deque.peek()) {
            deque.poll();
        }
    }

    // 入队操作:移除所有比当前元素小的尾部元素,保证队列单调递减
    void add(int val) {
        while (!deque.isEmpty() && val > deque.getLast()) {
            deque.removeLast();
        }
        deque.add(val);
    }

    // 获取当前窗口最大值(队首元素)
    int peek() {
        return deque.peek();
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        // 结果数组长度:n - k + 1
        int[] res = new int[n - k + 1];
        int index = 0;
        MyDeque myDeque = new MyDeque();

        // 1. 先初始化第一个窗口的元素
        for (int i = 0; i < k; i++) {
            myDeque.add(nums[i]);
        }
        // 记录第一个窗口的最大值
        res[index++] = myDeque.peek();

        // 2. 滑动窗口遍历后续元素
        for (int i = k; i < n; i++) {
            // 窗口右移,移除窗口最左侧的元素
            myDeque.poll(nums[i - k]);
            // 加入窗口新元素
            myDeque.add(nums[i]);
            // 记录当前窗口最大值
            res[index++] = myDeque.peek();
        }
        return res;
    }
}

核心亮点

  • 时间复杂度:O(n)(每个元素仅入队、出队一次)
  • 空间复杂度:O(k)(队列最多存储 k 个元素)
  • 面试必背:单调队列专门解决滑动窗口极值问题

76. 最小覆盖子串(Hard)

题目描述

给你一个字符串 s、一个字符串 t。返回 s 中涵盖 t 所有字符的最小子串;如果不存在,返回空字符串。

解题思路

这是滑动窗口的天花板题目 ,核心套路:双指针 + 双哈希表 + 精准计数

  1. 用两个哈希表:need 记录目标字符串 t 的字符需求,window 记录当前窗口内的字符;
  2. 右指针不断右移扩大窗口,直到窗口包含 t 所有字符;
  3. 左指针开始收缩窗口,尽可能缩小长度,同时更新最小子串;
  4. strnumber记录窗口内完全匹配的字符种类数,精准控制窗口收缩时机。

满分代码(极简注释版)

java

运行

复制代码
class Solution {
    public String minWindow(String s, String t) {
        // 边界特判
        if (s == null || t == null || s.isEmpty() || t.isEmpty()) {
            return "";
        }

        // need:记录目标字符串t的字符及数量
        Map<Character, Integer> need = new HashMap<>();
        // window:记录当前滑动窗口内的字符及数量
        Map<Character, Integer> window = new HashMap<>();
        // 滑动窗口左右指针
        int left = 0, right = 0;
        // 窗口内已匹配的字符种类数(和need完全匹配的数量)
        int matchCount = 0;
        // 记录最小子串的长度和起始索引
        int minLen = Integer.MAX_VALUE;
        int start = 0;

        // 初始化need哈希表
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        // 右指针扩大窗口
        while (right < s.length()) {
            char rightChar = s.charAt(right);
            right++;
            // 更新窗口内字符计数
            if (need.containsKey(rightChar)) {
                window.put(rightChar, window.getOrDefault(rightChar, 0) + 1);
                // 当前字符数量完全匹配,匹配数+1
                if (window.get(rightChar).equals(need.get(rightChar))) {
                    matchCount++;
                }
            }

            // 窗口已包含所有t的字符,开始收缩左指针,优化最小长度
            while (matchCount == need.size()) {
                // 更新最小子串
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }

                // 左指针右移,缩小窗口
                char leftChar = s.charAt(left);
                if (window.containsKey(leftChar)) {
                    window.put(leftChar, window.get(leftChar) - 1);
                    // 字符数量不匹配,匹配数-1
                    if (window.get(leftChar) < need.get(leftChar)) {
                        matchCount--;
                    }
                }
                left++;
            }
        }
        // 没有找到合法子串返回空,否则返回最小子串
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }
}

核心关键点

  1. 双哈希表解耦:严格区分目标字符和窗口字符,避免计数混乱;
  2. matchCount 精准控制 :只有字符数量完全相等时才计数,是收缩窗口的唯一依据;
  3. 收缩窗口优化:找到合法窗口后,尽可能缩小,保证得到最小子串。

刷题总结

  1. 滑动窗口最大值单调队列 专属场景,解决滑动窗口极值问题,队首恒为最值;
  2. 最小覆盖子串滑动窗口 + 哈希表经典 Hard 题,掌握「扩大窗口→匹配→收缩窗口」模板,同类题一通百通;
相关推荐
承渊政道1 天前
【优选算法】(实战推演模拟算法的蕴含深意)
数据结构·c++·笔记·学习·算法·leetcode·排序算法
林鸿群1 天前
实现支持纳秒级精度的时间引擎(C++)
算法·定时引擎
Keep learning!1 天前
PCA主成分分析学习
学习·算法
专注VB编程开发20年1 天前
CUDA实现随机切割算法,显卡多线程计算
算法·cuda
2301_788770551 天前
OJ模拟4
算法
NAGNIP1 天前
一文搞懂CNN经典架构-AlexNet!
人工智能·算法
2401_878530211 天前
自定义内存布局控制
开发语言·c++·算法
专注VB编程开发20年1 天前
PNG、GIF透明游戏角色人物输出一张图片技巧,宽度高度读取
算法
CoderCodingNo1 天前
【CSP】CSP-J 2025真题 | 异或和 luogu-P14359 (相当于GESP六级水平)
算法
keep intensify1 天前
打家劫舍3
算法·深度优先