【JS每日一算法】🟥78. 柱状图中最大的矩形(暴力破解、单调栈、单调栈优化)

491 阅读2分钟

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

提示:

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104

示例 1:

histogram.jpg

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

histogram-1.jpg

输入: heights = [2,4]
输出: 4

  题解:

更多JS版本题解点击链接关注该仓库👀

/**
 * @description: 暴力破解   TC:O(n^2)   SC:O(1)
 * @author: JunLiangWang
 * @param {*} heights 给定高度数组
 * @return {*}
 */                  
function bruteForce(heights) {
    /**
     * 本方案采用暴力破解的方式,遍历数组每个元素,并将该元素作为
     * 它组成矩形的最高高度,由此以它为中心向左右扩散(遍历),当遇
     * 到小于它的元素或到达边界则停止遍历,当遍历完成则计算出面积,
     * 该面积则为以它为最高高度组成的矩形面积,然后与当前记录最大
     * 面积比较,得出最大面积。
     */

    // 记录最大面积
    let maxArea = 0;
    // 遍历数组每个元素,并将该元素作为它组成矩形的最高高度
    for (let i = 0; i < heights.length; i++) {
        // 向左遍历指针
        let left = i - 1,
        // 向右遍历指针
            right = i + 1,
        // 宽度
            count = 1;
        // 向左扩散,当遇到小于当前元素的元素或到达边界则停止遍历
        while (left >= 0 && heights[left] >= heights[i]) {
            left--;
            count++;
        }
        // 向右扩散,当遇到小于当前元素的元素或到达边界则停止遍历
        while (right < heights.length && heights[right] >= heights[i]) {
            right++;
            count++;
        }
        // 计算面积与当前记录最大面积比较,得出最大面积。
        if (maxArea < count * heights[i]) maxArea = count * heights[i];
    }
    // 返回最大面积
    return maxArea;
}


/**
 * @description: 单调栈   TC:O(n)  SC:O(n)
 * @author: JunLiangWang
 * @param {*} heights 给定高度数组
 * @return {*}
 */
function monotonicStack(heights){
    /**
     * 上述暴力破解,当我们找某一高度的边界时,存在多次重复扫描
     * 上次已扫描过的元素,因此我们可以通过优化寻找边界的过程来
     * 优化时间复杂度
     * 
     * 我们可以定义两数组(leftBorder,rightBorder),heights[i]
     * 的左边界为leftBorer[i],右边界为rightBorder[i]。
     * 
     * 如何获得所有元素的左/右边界呢?此处我们以左边界举例子
     * 某一元素的左边界则为左边离它最近的小于它的元素,因此
     * 我们可以利用栈,从左到右遍历数组元素,不断将元素入栈,栈顶元素
     * 为当前元素的上一个元素,当栈顶元素大于等于当前元素证明
     * 上一个元素不是当前元素的左边界,将元素出栈,继续比较栈
     * 顶元素(此时栈顶元素则为上一个元素的左边界,即为小于的它
     * 元素,那些上次被出栈的元素肯定是大于等于上一个元素的,因此无需
     * 再次比较),直到栈为空或者找到了小于当前元素的栈顶元素,
     * 如果栈为空证明当前元素的左边界超过了数组范围为-1,如果栈
     * 不为空,证明当前元素的左边界为栈顶元素,最后我们把当前元素
     * 入栈,继续遍历数组下一个元素
     * 
     * 右边界呢?我们重复上述过程,从右到左遍历数组即可,超出数组范围
     * 不再赋值为-1,而是赋值为数组长度
     * 
     * 最后我们通过(rightBorder[i]-leftBorder[i]-1)*heights[i]计算出
     * 每个元素作为最大高度的矩形的面积,比较出最大的面积即可获取答案
     */

    // 左边界数组
    let leftBorder=new Array(heights.length),
    // 右边界数组
    rightBorder=new Array(heights.length), 
    // 栈
    stack=[],
    // 最大面积
    maxArea=0;
    // 从左到右遍历高度,获取左边界
    for(let i=0;i<heights.length;i++){
        // 当栈顶元素大于等于当前元素,证明不是当前元素的左边界,此时出栈
        while(stack.length>0&&heights[stack[stack.length - 1]]>=heights[i])stack.pop()
        // 当栈为空,证明当前左边界超出数组范围,置为-1
        if(stack.length==0)leftBorder[i]=-1;
        // 不为空,则此时栈顶元素是小于当前元素的,为当前元素的左边界
        else leftBorder[i]=stack[stack.length - 1];
        // 给栈添加当前元素
        stack.push(i);
    }
    // 将栈置为空
    stack=[];
    // 从右到左遍历高度,获取右边界(与上述获取左边界同理,只不过超过范围置为数组长度)
    for(let i=heights.length-1;i>=0;i--){
        while(stack.length>0&&heights[stack[stack.length - 1]]>=heights[i])stack.pop()
        if(stack.length==0)rightBorder[i]=heights.length;
        else rightBorder[i]=stack[stack.length - 1];
        stack.push(i);
    }
    // 遍历左右边界,计算面积,获取最大面积
    for(let i=0;i<heights.length;i++){
        let area=(rightBorder[i]-leftBorder[i]-1)*heights[i]
        if(area>maxArea)maxArea=area;
    }
    // 返回结果
    return maxArea
}


/**
 * @description: 单调栈优化  TC:O(n)  SC:O(n)
 * @author: JunLiangWang
 * @param {*} heights 给定高度数组
 * @return {*}
 */
function monotonicStackOptimization(heights){
    /**
     * 上述单调栈通过两个循环获取了所有元素的左/右边界,
     * 其实我们可以通过一个循环即可获取所有元素的左/右
     * 边界,在上述单调栈方法中在我们获取元素左边界时
     * 当栈顶元素大于等于当前元素时会弹出栈顶元素,试想
     * 此时当前元素不就是小于等于了栈顶元素吗?栈顶元素的右
     * 边界不就是当前元素了吗。
     * 
     * 通过该思路我们可以继续常级优化获取所有元素左/右边界
     * 的过程。
     */

    // 记录左边界数组,所有元素初始化为-1
    let leftBorder=new Array(heights.length).fill(-1),
    // 记录右边界数组,所有元素初始化为数组长度
    rightBorder=new Array(heights.length).fill(heights.length), 
    // 栈
    stack=[],
    // 最大面积
    maxArea=0;

    // 从左到右遍历高度,获取左/右边界
    for(let i=0;i<heights.length;i++){
        
        // 当栈顶元素大于等于当前元素,证明栈顶元素不是当前元素的左边界,
        // 而当前元素则是栈顶元素的右边界,记录栈顶元素右边界,然后出栈
        while(stack.length>0&&heights[stack[stack.length - 1]]>=heights[i]){
             rightBorder[stack[stack.length - 1]]=i
             stack.pop()
        }
        // 当栈不为空,则此时栈顶元素是小于当前元素的,为当前元素的左边界
        if(stack.length>0)leftBorder[i]=stack[stack.length - 1];
        // 否则栈为空,证明当前元素左边界超出数组范围,需要置为-1,
        // 由于默认值为-1,因此无需重新赋值

        // 给栈添加当前元素
        stack.push(i);
    }
    // 遍历左右边界,计算面积,获取最大面积
    for(let i=0;i<heights.length;i++){
        let area=(rightBorder[i]-leftBorder[i]-1)*heights[i]
        if(area>maxArea)maxArea=area;
    }
    // 返回最大面积
    return maxArea
}

来源:力扣(LeetCode)