跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

单调栈专题:接雨水与柱状图中最大矩形

详细讲解了单调栈在接雨水与柱状图中最大矩形两道经典算法题中的应用。通过对比暴力法、双指针优化及单调栈三种解法,重点分析了单调栈如何高效寻找左右边界,将时间复杂度优化至 O(n)。文章提供了完整的 Java 与 C++ 代码实现及思路解析,适合算法学习者深入理解数据结构在解题中的核心作用。

落日余晖发布于 2026/3/21更新于 2026/6/2642 浏览

题目列表

接雨水 42. 接雨水 - 力扣(LeetCode)

柱状图中最大的矩形 84. 柱状图中最大的矩形 - 力扣(LeetCode)

一、接雨水

1. 暴力法思路

暴力解法的本质是逐个位置计算接水量,步骤如下:

  • 遍历数组中每一个位置(首尾位置无法接水,直接跳过);
  • 对每个位置 i:
    • 找 i 左边(包括 i)的最大高度 maxLeft;
    • 找 i 右边(包括 i)的最大高度 maxRight;
    • 计算该位置的接水量:min(maxLeft, maxRight) - height[i](结果≥0 才有效);
  • 累加所有位置的接水量,得到总水量。

2. 暴力法实现代码

class Solution {
    public int trap(int[] height) {
        int len = height.length;
        int sum = 0;
        for (int i = 0; i < len; i++) {
            if (i == 0 || i == len - 1) {
                continue;
            }
            int maxLeft = height[i];
            int maxRight = height[i];
            for (int j = i; j >= 0; j--) {
                if (height[j] > maxLeft) {
                    maxLeft = height[j];
                }
            }
            for (int j = i; j < len; j++) {
                if (height[j] > maxRight) {
                    maxRight = height[j];
                }
            }
            sum += Math.min(maxLeft, maxRight) - height[i];
        }
        return sum;
    }
}

3. 双指针优化思路

暴力解法的最大问题是:每个位置都要重复遍历左右找最大高度。优化的核心是预处理两个数组:

  • maxLeft[i]:表示位置 i 及左边的最大高度;
  • maxRight[i]:表示位置 i 及右边的最大高度; 后续计算直接从预处理数组取值,无需重复遍历。

4. 双指针优化实现代码

class Solution {
    public int trap(int[] height) {
        int len = height.length;
        if (len <= 2) return 0;
        int sum = 0;
        int[] maxLeft = new int[len];
        maxLeft[0] = height[0];
        for (int i = 1; i < len; i++) {
            maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
        }
        int[] maxRight = new int[len];
        maxRight[len - 1] = height[len - 1];
        for (int i = len - 2; i >= 0; i--) {
            maxRight[i] = Math.max(height[i], maxRight[i + 1]);
        }
        for (int i = 0; i < len; i++) {
            int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
            if (count > 0) sum += count;
        }
        return sum;
    }
}

5. 单调栈思路

单调栈解法的核心逻辑:

  1. 栈的定义:存储索引,对应高度单调递减。
  2. 接水量计算:当遇到比栈顶高的元素时,说明找到凹槽右边界。弹出栈顶作为底部,新栈顶为左边界,当前索引为右边界。
    • 高度:min(height[left], height[right]) - height[mid]
    • 宽度:right - left - 1
  3. 流程:遍历数组,若当前高度大于栈顶则循环弹出计算,否则压栈。

注:单调栈接水量是按行来计算的,而双指针是按列来计算的。

6. 单调栈实现代码

import java.util.Deque;
import java.util.LinkedList;

class Solution {
    public int trap(int[] height) {
        int len = height.length;
        if (len <= 2) return 0;
        int sum = 0;
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        for (int right = 1; right < len; right++) {
            if (height[right] <= height[stack.peek()]) {
                stack.push(right);
            } else {
                while (!stack.isEmpty() && height[right] > height[stack.peek()]) {
                    int mid = stack.pop();
                    if (stack.isEmpty()) break;
                    int left = stack.peek();
                    int h = Math.min(height[left], height[right]) - height[mid];
                    int w = right - left - 1;
                    sum += h * w;
                }
                stack.push(right);
            }
        }
        return sum;
    }
}

二、柱状图中最大的矩形

1. 暴力法思路

遍历每一根柱子,向左向右找第一个高度更小的柱子作为边界,计算面积并取最大值。

2. 暴力法实现代码

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int sum = 0;
        for (int i = 0; i < heights.size(); i++) {
            int left = i;
            int right = i;
            for (; left >= 0; left--) {
                if (heights[left] < heights[i]) break;
            }
            for (; right < heights.size(); right++) {
                if (heights[right] < heights[i]) break;
            }
            int w = right - left - 1;
            int h = heights[i];
            sum = max(sum, w * h);
        }
        return sum;
    }
};

3. 双指针思路

通过两个数组提前计算每个柱子的左右边界,避免重复遍历,将时间复杂度从 O(n²) 降到 O(n)。

  • leftMin[i]:左侧第一个更矮柱子的索引(无则为 -1);
  • rightMin[i]:右侧第一个更矮柱子的索引(无则为 n);
  • 宽度:rightMin[i] - leftMin[i] - 1。

4. 双指针实现代码

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        if (n == 0) return 0;
        if (n == 1) return heights[0];
        int[] leftMin = new int[n];
        leftMin[0] = -1;
        for (int i = 1; i < n; i++) {
            int t = i - 1;
            while (t >= 0 && heights[t] >= heights[i]) {
                t = leftMin[t];
            }
            leftMin[i] = t;
        }
        int[] rightMin = new int[n];
        rightMin[n - 1] = n;
        for (int i = n - 2; i >= 0; i--) {
            int t = i + 1;
            while (t < n && heights[t] >= heights[i]) {
                t = rightMin[t];
            }
            rightMin[i] = t;
        }
        int maxArea = 0;
        for (int i = 0; i < n; i++) {
            int width = rightMin[i] - leftMin[i] - 1;
            int area = heights[i] * width;
            maxArea = Math.max(maxArea, area);
        }
        return maxArea;
    }
}

5. 单调栈思路

使用「单调递增栈」实时找每个柱子的左右边界。哨兵的作用:头部哨兵确保栈不空,末尾哨兵强制弹出剩余元素。

  • 栈内存储索引,高度递增。
  • 弹出栈顶时,当前索引为右边界,新栈顶为左边界。

6. 单调栈实现代码

class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 1) return heights[0];
        int[] newHeights = new int[len + 2];
        for (int i = 0; i < len; i++) {
            newHeights[i + 1] = heights[i];
        }
        newHeights[0] = 0;
        newHeights[len + 1] = 0;
        Deque<Integer> st = new LinkedList<>();
        st.push(0);
        int res = 0;
        for (int i = 0; i < newHeights.length; i++) {
            while (newHeights[i] < newHeights[st.peek()]) {
                int mid = st.pop();
                int right = i;
                int left = st.peek();
                int w = right - left - 1;
                int h = newHeights[mid];
                res = Math.max(res, h * w);
            }
            st.push(i);
        }
        return res;
    }
}

目录

  1. 题目列表
  2. 一、接雨水
  3. 1. 暴力法思路
  4. 2. 暴力法实现代码
  5. 3. 双指针优化思路
  6. 4. 双指针优化实现代码
  7. 5. 单调栈思路
  8. 6. 单调栈实现代码
  9. 二、柱状图中最大的矩形
  10. 1. 暴力法思路
  11. 2. 暴力法实现代码
  12. 3. 双指针思路
  13. 4. 双指针实现代码
  14. 5. 单调栈思路
  15. 6. 单调栈实现代码
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 面向 C++ 开发的 Web 自动化测试入门:从概念到 Selenium 实战
  • C++ 仿函数详解:让对象像函数一样调用
  • C++ 仿函数核心解析:状态、性能与 STL 实践
  • C++ 仿函数深度解析:状态管理与 STL 实践
  • C++ 二叉搜索树详解:概念、操作与实现
  • C++ 二叉搜索树(BST)原理及核心操作实现
  • C++ 二叉搜索树:从基础概念到代码实现
  • C++ 多线程同步:原子操作 atomic 实战
  • 大模型基础知识:分词与提示工程详解
  • C++ 多线程同步实战:原子操作 atomic 详解
  • Visual C++ Release 模式使用 Win32 API 导出崩溃堆栈
  • C++ 多线程同步:原子操作(atomic)实战
  • C++ 多态核心原理与实现机制
  • C++ 读取 INI 配置文件详解与源码实现
  • C++ STL 容器适配器详解:Stack、Queue、Priority Queue 原理与实战
  • 2025 华为 OD 机试真题题库汇总及 OJ 刷题指南
  • Spring Boot 虚拟线程时代:WebFlux 与 WebMVC 选型指南
  • 基于 DeepSeek 的贪吃蛇游戏开发实战
  • 阿里云 MoltBot 机器人钉钉 Stream 流式接入配置
  • 5 款好用的免费在线代码编辑器

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online