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

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

一、核心思想

单调栈单调队列 通过维护一个元素按特定顺序(递增或递减)排列的结构,将某些问题的复杂度从 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)

适用场景

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

核心思维导图

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

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

相关推荐
Yeats_Liao3 小时前
评估体系构建:基于自动化指标与人工打分的双重验证
运维·人工智能·深度学习·算法·机器学习·自动化
cpp_25013 小时前
P9586 「MXOI Round 2」游戏
数据结构·c++·算法·题解·洛谷
浅念-3 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
有时间要学习3 小时前
面试150——第五周
算法·深度优先
晚霞的不甘4 小时前
Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
人工智能·算法·flutter·架构·开源·音视频
望舒5134 小时前
代码随想录day25,回溯算法part4
java·数据结构·算法·leetcode
C++ 老炮儿的技术栈5 小时前
Qt 编写 TcpClient 程序 详细步骤
c语言·开发语言·数据库·c++·qt·算法
KYGALYX5 小时前
逻辑回归详解
算法·机器学习·逻辑回归
铉铉这波能秀5 小时前
LeetCode Hot100数据结构背景知识之集合(Set)Python2026新版
数据结构·python·算法·leetcode·哈希算法
踢足球09295 小时前
寒假打卡:2026-2-8
数据结构·算法