高阶算法技巧分享三:单调栈与单调队列详解

高阶算法技巧:单调栈与单调队列详解

一、核心思想

单调栈单调队列 通过维护一个元素按特定顺序(递增或递减)排列的结构,将某些问题的复杂度从 O(n²) 优化到 O(n)。核心在于及时排除无效元素,减少不必要的计算


二、单调队列案例:滑动窗口最大值

问题描述 :给定数组和窗口大小 k,求每个窗口的最大值。
暴力法问题:遍历每个窗口找最大值,时间复杂度 O(nk)。

优化思路:维护一个单调递减队列,队头为当前窗口最大值。

步骤分解

  1. 队列中存储元素索引,保证对应值递减。
  2. 新元素入队前,移除队尾比它小的元素。
  3. 检查队头是否在窗口内,否则移除。
  4. 当窗口形成后(i ≥ k-1),记录队头值。

图解

ini 复制代码
数组:[1,3,-1,-3,5,3,6,7], k=3
窗口位置         队列内容(索引)        最大值
[1 3 -1]        [1,2](值3,-1)       3
1 [3 -1 -3]     [1,2,3](3,-1,-3)    3
1 3 [-1 -3 5]   [4](5)              5
...

代码示例

java 复制代码
import java.util.*;

public class SlidingWindowMax {
    public static int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> q = new ArrayDeque<>();
        List<Integer> result = new ArrayList<>();

        for (int i = 0; i < nums.length; i++) {
            // 维护单调递减性
            while (!q.isEmpty() && nums[q.peekLast()] <= nums[i]) {
                q.pollLast();
            }
            q.offerLast(i);

            // 移除越界的队首元素
            if (q.peekFirst() == i - k) {
                q.pollFirst();
            }

            // 窗口形成后记录结果
            if (i >= k - 1) {
                result.add(nums[q.peekFirst()]);
            }
        }

        return result.stream().mapToInt(Integer::intValue).toArray();
    }

    public static void main(String[] args) {
        int[] nums = {1,3,-1,-3,5,3,6,7};
        System.out.println(Arrays.toString(maxSlidingWindow(nums, 3)));
        // 输出: [3,3,5,5,6,7]
    }
}

三、单调栈案例1:接雨水

问题描述:计算柱子排列后能接的雨水总量。

优化思路:维护单调递减栈,遇到较高柱子时计算积水。

步骤分解

  1. 栈保存索引,对应高度递减。
  2. 当前高度 > 栈顶高度时,弹出栈顶作为坑底。
  3. 新栈顶为左边界,计算宽度和高度差。

图解

scss 复制代码
高度:[0,1,0,2,1,0,1,3,2,1,2,1]
处理索引2(高度0)时,栈为[1(1)] → 无积水。
处理索引3(高度2)时,弹出0(0),左边界1(1),积水宽度3-1-1=1,高度min(2,1)-0=1 → 积水量1×1=1。

代码示例

java 复制代码
import java.util.*;

public class TrappingRainWater {
    public static int trap(int[] height) {
        Stack<Integer> stack = new Stack<>();
        int water = 0;
        
        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int bottom = stack.pop();
                if (stack.isEmpty()) break;
                
                int left = stack.peek();
                int width = i - left - 1;
                int depth = Math.min(height[i], height[left]) - height[bottom];
                water += width * depth;
            }
            stack.push(i);
        }
        return water;
    }

    public static void main(String[] args) {
        int[] height = {0,1,0,2,1,0,1,3,2,1,2,1};
        System.out.println(trap(height)); // 输出: 6
    }
}

四、单调栈案例2:柱状图中的最大矩形

问题描述:找到柱状图中最大的矩形面积。

优化思路:对每个柱子,找到左右第一个比它矮的边界。

步骤分解

  1. 维护递增栈,存储(索引,高度)。
  2. 当前高度 < 栈顶高度时,确定栈顶的右边界。
  3. 左边界为栈中前一个元素,计算面积。

图解

scss 复制代码
高度:[2,1,5,6,2,3]
处理索引4(高度2)时,弹出6(3)→左边界2(5),面积6*(4-2-1)=6。
继续弹出5(2)→左边界-1,面积5*4=20。

代码示例

java 复制代码
import java.util.*;

public class LargestRectangle {
    public static int largestRectangleArea(int[] heights) {
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(-1); // 哨兵
        int maxArea = 0;
        int[] extended = Arrays.copyOf(heights, heights.length + 1);
        extended[heights.length] = 0; // 触发最终计算

        for (int i = 0; i < extended.length; i++) {
            while (stack.peek() != -1 && extended[i] < extended[stack.peek()]) {
                int h = extended[stack.pop()];
                int w = i - stack.peek() - 1;
                maxArea = Math.max(maxArea, h * w);
            }
            stack.push(i);
        }
        return maxArea;
    }

    public static void main(String[] args) {
        int[] heights = {2,1,5,6,2,3};
        System.out.println(largestRectangleArea(heights)); // 输出: 10
    }
}

五、总结与适用场景

优势

  • 时间复杂度从 O(n²) → O(n)
  • 空间复杂度 O(n)

适用场景

  • 滑动窗口最值 → 单调队列
  • 边界查找问题(雨水、矩形)→ 单调栈
  • 其他变种:下一个更大元素、股票跨度等

核心思维导图

复制代码
单调结构 → 维护有序性
   ├─ 队列:动态窗口,淘汰旧元素
   └─ 栈:历史数据,找到最近更大/小元素

掌握单调栈/队列后,可高效解决一系列看似复杂的问题,关键在于识别问题中的"无效元素"并及时剔除,聚焦核心计算。

相关推荐
CoovallyAIHub6 小时前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP7 小时前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo7 小时前
半开区间和开区间的两个二分模版
算法
moonlifesudo7 小时前
300:最长递增子序列
算法
CoovallyAIHub12 小时前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub13 小时前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉
聚客AI1 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
大怪v1 天前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法
惯导马工1 天前
【论文导读】ORB-SLAM3:An Accurate Open-Source Library for Visual, Visual-Inertial and
深度学习·算法
骑自行车的码农1 天前
【React用到的一些算法】游标和栈
算法·react.js