【leetcode】84 柱状图中最大的矩形

题目信息

来源及链接

84. 柱状图中最大的矩形
图片来源于leetcode

解题

解析思路

通过单调栈实现。
关键点:要找到当前位置值左侧和右侧第一个小于该值的位置。
分别从左到右和从右到左使用单调栈计算位置。
栈中存储的数据是元素的位置。(需要通过栈中的数据比较大小和确定位置,所以存储位置同时满足两个条件。)

为什么是单调栈?
为了找到左右两边小于自身的值,暴力解法是通过遍历去查找。为了避免额外的遍历,如果我们能在第一次遍历的时候保存已经遍历过的数据大小关系,就能不用把已经遍历过的数据再遍历一次。
这要求这种数据结构能够保存数据,并且数据可以是有序的。其实vector、deque、stack都可以满足,(stack的底层可以是deque或者vector实现),使用stack是因为stack的方法操作最方便。
那么stack里面存放什么数据,这个数据的意义又是什么?
为了能够记录数据的大小关系,stack中的数据需要是有序的。自然会想到,数据要么是从小到大,要么是从大到小。这两种关系又代表了什么?

单调栈是单调递增还是单调递减?
从小到大:我们每次放入stack中的数据,都要比栈顶的元素大,也是比栈中所有的元素都大。那么,对于当前要进栈的这个元素,栈中存放的数据就是之前遍历的所有比当前位置小的数据。那么对于已经存放的数据,每个元素之前的那个元素,就是这个元素左边第一个比他小的元素。
反之,如果按照从大到小的单调栈顺序,那么每个存放的元素,前一个位置的元素就是左边第一个比自己大的元素。(一定要动手模拟一遍)
在此题中,我们需要找到左边第一个比自己小的元素,自然是使用单调递增的栈。

进栈时记录数据还是出栈时记录数据?
都可,不影响。
事实上,一次遍历中,每个元素都要进栈(为了找到前一个小于自己 的值,并且还要保存自己,判断自己是不是之后某个元素的前一个最小值),但遍历完了之后,栈中可能还有元素没有出栈(极端情况数组单调递增,则所有元素都进栈)。
如果选择进栈的时候记录数据,则遍历完成之后就可以确定结果,但是如果选择出栈的时候记录数据,则遍历完成之后还要进行出栈操作。相比之下,显然进栈是记录数据会更便捷。

相同的元素如何处理?
对于单调递增的栈,进栈时,如果前一个元素和当前元素相等,这时前一个元素要出栈吗?
体现在代码中就是,在判断时是用 >= 还是 > ?

heights[si.top()] >= heights[i]
heights[si.top()] > heights[i]

首先要明白栈中存放元素的意义,对于本题,我们是为了找到当前元素左边第一个小于自己的值。那么我们是如何得到这个值的?是取的这个值存放到栈里面之后坐标第一个元素。那就很明确了,如果栈里面有两个相同的值,那么后进栈的这个元素在判断时,会将前一个和自己一样元素判定为“左边第一个小于自己的值”,从而发生错误。
那么栈中不应该存放两个相同的值,栈应该是严格递增的。在判断是否需要出栈时,大于或者等于当前判断元素的数据都要出栈。

栈中存放下标还是值本身?
就本题而言,是为了记录前一个小于自己的值的位置,那么需要存放下标(过程中会比较元素的大小,而且我们还需要最终获取下标)。如果是需要找到前一个小于自身的值,那么就存放值本身。

实现

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        size_t num = heights.size();
        if (num == 0) {
            return 0;
        }
        
        vector<int> left_dis(num), right_dis(num);

        // get a stack
        stack<int> si;
        for (int i = 0; i < num; ++i) {
            // if stack not empty, check value
            while(!si.empty() && heights[si.top()] >= heights[i]) {
                si.pop();
            }
            if (si.empty()) {
                left_dis[i] = -1;
            } else {
                left_dis[i] = si.top();
            }
            si.push(i);
        }

        stack<int> si2;
        for (int i = num - 1; i >= 0; --i) {
            while(!si2.empty() && heights[si2.top()] >= heights[i]) {
                si2.pop();
            }
            if (si2.empty()) {
                right_dis[i] = num;
            } else {
                right_dis[i] = si2.top();
            }
            si2.push(i);
        }

        int max_val = 0;
        for (int i = 0; i < num; ++i) {
            int value = (right_dis[i] - left_dis[i] - 1) * heights[i];
            max_val = max(max_val, value);
        }
        return max_val;
    }
};

思考总结

从暴力解法入手,一步步分析。
单调栈可以用于O(N)的时间复杂度求解一侧的最大值或者最小值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值