栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

LeetCode 题解随笔:单调栈

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

LeetCode 题解随笔:单调栈

目录

739. 每日温度

496. 下一个更大元素 I

503. 下一个更大元素 II

42. 接雨水[*]

84. 柱状图中最大的矩形[*]


一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时考虑用单调栈。时间复杂度为O(n)。

单调栈里存放的元素:元素的下标i,如果需要使用对应的元素,直接T[i]就可以获取。

单调栈里元素顺序:从栈头到栈底递增/递减。

使用单调栈主要有三个判断条件:

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

739. 每日温度
vector dailyTemperatures(vector& temperatures) {
        // 结果序列初始化为0,若未更新说明右侧没有比其更大的元素
        vector res(temperatures.size(), 0);
        // 构建单调栈:从栈头到栈尾递增,装入元素下标
        stack st;
        st.push(0);
        for (int i = 1; i < temperatures.size(); i++) {
            // 出现新元素大于栈顶元素,记录结果并弹出栈顶元素,保持栈的单调
            // 注意栈不能为空,若为空新元素直接入栈
            while (!st.empty() && (temperatures[i] > temperatures[st.top()])) {
                // res记录右侧出现第一个更大元素的位置
                res[st.top()] = i - st.top();
                st.pop();
            }
            // 新元素小于等于栈顶元素时直接入栈
            st.push(i);
        }
        return res;
    }

本题构建了一个从栈头到栈尾递增的单调栈,注意栈内元素是数组元素的下标。新入栈元素对应温度一定比栈头元素对应温度小,否则更新结果(下标相减即为右侧第一次出现更大元素的位置),并弹出栈头,入栈更大的元素,从而保证栈的单调性。

496. 下一个更大元素 I
vector nextGreaterElement(vector& nums1, vector& nums2) {
        vector res(nums1.size(), -1);
        // 存放nums1中元素便于查找,需要得到元素的下标,因此用unordered_map【元素/下标】
        unordered_map index;
        for (int i = 0; i < nums1.size(); i++) {
            index.insert(make_pair(nums1[i], i));
        }
        // 创建单调栈:从栈头到栈尾递增
        stack st;
        for (int i = 0; i < nums2.size(); i++) {
            while (!st.empty() && nums2[i] > nums2[st.top()]) {               
                if (index.find(nums2[st.top()]) != index.end()) {
                    // index[nums2[st.top()]]:目标元素在nums1中的下标
                    res[index[nums2[st.top()]]] = nums2[i];
                }
                st.pop();
            }
            st.push(i);
        }
        return res;
    }

本题和上一题基本类似,定义了一个unordered_map便于确认nums2中的元素是否在nums1中存在,同时获取其在nums1中下标,以便于更新结果。

503. 下一个更大元素 II
vector nextGreaterElements(vector& nums) {
        vector res(nums.size(), -1);
        // 创建单调栈:从栈头到栈尾递增
        stack st;
        // 循环数组:模拟执行两遍找右侧第一个出现的较大值的操作
        for (int i = 0; i < nums.size() * 2; i++) {
            while (!st.empty() && nums[i % nums.size()] > nums[st.top()]) {
                res[st.top()] = nums[i % nums.size()];
                st.pop();
            }
            st.push(i % nums.size());
        }
        return res;
    }

 本题在nums数组后再追加一个nums数组,就可以利用前述单调栈的方法完成循环数组的求解。这里利用了i % nums.size()作为下标索引,来模拟执行两遍的操作。

42. 接雨水[*]
  • 动态规划法:
int trap(vector& height) {
        // 动态规划法:利用状态转移求解每个位置左右两侧的最大高度
        int res = 0;
        if (height.size() <= 2) return res;
        vector maxLeftDP(height.size(), 0);
        vector maxRightDP(height.size(), 0);
        // 求解右侧(含本柱子)最大高度:后一个位置的右侧最大高度和本高度的最大值
        // 这样设置便于求解,若本柱子最高,本柱子积水结果直接为0
        maxRightDP[height.size() - 1] = height[height.size() - 1];
        for (int i = height.size() - 2; i >= 0; i--) {
            maxRightDP[i] = max(height[i], maxRightDP[i + 1]);
        }
        // 求解左侧(含本柱子)最大高度:前一个位置的左侧最大高度和本高度的最大值
        maxLeftDP[0] = height[0];
        for (int i = 1; i < height.size(); i++) {
            maxLeftDP[i] = max(height[i], maxLeftDP[i - 1]);
        }
        for (int i = 0; i < height.size(); i++) {
            res += min(maxLeftDP[i], maxRightDP[i]) - height[i];
        }
        return res;
    }

本题可以转化为求某个柱子左右两侧(含本柱子)的最大柱子高度,两者的最小值与本柱子高度之差,就是本柱子上能积累的雨水,如下图所示(来源:代码随想录):

  •  单调栈:
int trap(vector& height) {
        // 单调栈法:求取横向雨水的面积
        int res = 0;
        if (height.size() <= 2)  return res;
        // 构造单调栈:栈底到栈顶元素递减,记录柱子下标
        stack st;
        for (int i = 0; i < height.size(); i++) {
            // 用新元素-栈顶元素-栈顶元素的后一个元素这三者来盛水
            while (!st.empty() && height[i] > height[st.top()]) {
                // 记录最低处位置
                int bottle = st.top();               
                st.pop();
                // 栈顶元素的后一个元素存在,出现低洼
                if (!st.empty()) {
                    // 雨水横向宽度
                    int w = i - st.top() - 1;
                    // 雨水纵向高度
                    int h = min(height[st.top()], height[i]) - height[bottle];
                    res += w * h;
                }
            }
            // 新元素等于栈顶元素时,出栈栈顶元素而入栈新元素,因为利用低洼处右边界计算雨水宽度
            if (!st.empty() && height[i] == height[st.top()]) {
                st.pop();
            }
            st.push(i);
        }
        return res;
    }

要把该问题转化为利用单调栈求解右侧出现的第一个更大元素的问题,必须转换一下思路,求低洼处积水的横向面积,如下图所示(来源:代码随想录):

 具体思路并不难,但要注意几处细节的处理:

  • 出现较大元素时,先记录栈顶元素(即最低位置),然后弹出栈顶元素,判断栈是否为空,即左侧是否有更大的元素以组成低洼;
  • 单调栈记录了柱子位置的下标,由于计算的是积水横向面积,就算上一个低洼处已被弹出(如图中第二个水坑处),也不影响下一次计算的结果;
  • 遇到相同的元素,要弹出旧的栈顶,保留更右侧的元素,因为积水的宽度是通过栈顶元素下一个元素(若有多个重复元素)的最右侧元素决定的。

84. 柱状图中最大的矩形[*]

本题首先要明白如何构建可勾勒的最大矩形,拥有一个自己的构建方式。对每一个柱子,期望其高度可以被充分利用,从而求这个柱子左右两侧首次出现的比其高度更低的柱子的位置为right和left,从而每个位置下可以获得的最大面积为:

sum = heights[i] * (right[i] - left[i] - 1)
  • 动态规划法:
int largestRectangleArea(vector& heights) {
        // 动态规划法:求出每个位置(含当前位置)左右两侧出现更小元素的下标
        vector minRightDP(heights.size());
        vector minLeftDP(heights.size());
        // 左边第一个小于当前元素的下标
        minLeftDP[0] = -1;
        for (int i = 1; i < heights.size(); i++) {
            int t = i - 1;
            while (t >= 0 && heights[t] >= heights[i]) {
                t = minLeftDP[t];
            }
            minLeftDP[i] = t;
        }
        // 右边第一个小于当前元素的下标
        minRightDP[heights.size() - 1] = heights.size();
        for (int i = heights.size() - 2; i >= 0; i--) {
            int t = i + 1;
            while (t < heights.size() && heights[t] >= heights[i]) {
                t = minRightDP[t];
            }
            minRightDP[i] = t;
        }
        int res = 0;
        for (int i = 0; i < heights.size(); i++) {
            int sum = heights[i] * (minRightDP[i] - minLeftDP[i] - 1);
            res = max(sum, res);
        }
        return res;
    }

利用状态转移找到左右两侧第一个小于当前元素的下标位置,注意状态转移公式的迭代方向和初始化(是为了避免死循环和符合求解的物理意义)。

  • 单调栈:
// 单调栈:从栈尾到栈头递增,记录元素下标
        stack st;
        // 数组头部和尾部插入0,以保证单元素/递增序列有结果
        heights.insert(heights.begin(), 0);
        heights.push_back(0);
        int res = 0;
        for (int i = 0; i < heights.size(); i++) {
            while (!st.empty() && heights[i] < heights[st.top()]) {
                int top = st.top();
                st.pop();
                int w = i - st.top() - 1;
                int h = heights[top];
                res = max(res, w * h);
            }
            st.push(i);
        }
        return res;

本题采用单调栈法与上一题很类似,“接雨水”是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。栈顶和栈顶的下一个元素以及要入栈的三个元素组成了要求的最大面积的高度和宽度。

为了能够处理单个柱子/单调递增序列的结果,在数组前后都添加了0。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/971533.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号