leetcode84- 柱状图中最大的矩形(三种思路:暴力,单调栈+哨兵(详解),分治)

介绍

我的LeetCode主页,一题一题解

标签:贪心

84. 柱状图中最大的矩形
难度 困难

84. 柱状图中最大的矩形
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

题目

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

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

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

示例:

输入: [2,1,5,6,2,3]
输出: 10





解题思路

虽然我也进行的单调栈的详解,但是看我的之前可以看看liweiwei1419大佬的题解以及官方题解的视频


解法一:暴力向两边搜索

对该柱子两边进行遍历,寻找以当前高度可以取得的最大面积,看一眼应该就懂了
image.png


解法二:单调栈

构建一个递增的单调栈,因为:

  • 如果后一个柱高度比目前柱更高,说明矩形面积还有增加的可能性
  • 如果后一个柱高度比目前柱更矮,说明已经没有增长空间了,可以出栈计算能形成的最大面积

画图演示

  1. heights[0] 因为是第一个所以直接入栈
    image.png

  2. 遍历到heights[1]比heights[0]更矮,计算可得heights[0]的最大面积,然后将heights[1]入栈
    image.png

  3. 遍历到heights[2],发现heights[1]更矮,因此heights[2]直接入栈
    image.png

  4. 遍历到heights[3],发现heights[2]更矮,因此heights[3]也直接入栈
    image.png

  5. 遍历到heights[4],发现当前栈顶元素heights[3]更高,则将其出栈,并计算其最大面积
    image.png

  6. 继续比较会发现当前栈顶元素heights[2]还是比heights[4]大,继续出栈并计算最大面积
    image.png

  7. 遍历到最后一个heights[5],发现heights[4]更矮,heights[4]入栈
    image.png

  8. 遍历已经到最末尾了,将heights[5]也入栈,准备将栈内元素全部清空了
    image.png

宽度计算:

宽度计算全部发生在出栈的情况下,并且有两个情况:

  1. 栈内没有其他元素了,那么一直到heights[0]都可以在当前高度下形成矩形
  2. 栈内还有其他元素,那么直到次栈顶元素,这段区间都是可以形成矩形的

解法三:单调栈+哨兵

因为在解法二中考虑了栈空与结尾栈不为空的情况,导致了造成了冗余,所以就在输入数组的两端加上两个高度为0的柱子,使之为**“哨兵”**

有了哨兵之后:

  1. 数组第一位的哨兵会使栈一定不会为空
  2. 数组最后位的哨兵会使前面全部有高度的柱出栈

从而可以保证将全部的柱能形成的高度全部计算一遍


解法四:分治,这是最快的方法

将数组分为最矮柱左边的部分与最矮柱右边的部分
然后三个中求最大

  1. 最大宽度*最小高度
  2. 最矮柱左边能形成的最大面积
  3. 最矮柱右边能形成的最大面积

同时对数组段是否递增进行判断,如果一旦是单调递增,那么最大的面积毫无疑问就是最矮柱高度*当前宽度



代码

解法一:暴力

public class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0)  return 0;

        int res = 0;
        for (int i = 0; i < len; i++) {
            //对该柱子两边进行遍历,寻找以当前高度可以取得的最大面积
            int hight = heights[i];

            // 向左找
            int left = i;
            while (left > 0 && heights[left - 1] >= hight) {
                left--;
            }

            // 向右找
            int right = i;
            while (right < len - 1 && heights[right + 1] >= hight) {
                right++;
            }

            int sum = right - left + 1;
            res = Math.max(res, sum * hight);
        }
        return res;
    }
}

这么慢的吗…
image.png


解法二:单调栈

public class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0) {
            return 0;
        }
        if (len == 1) {
            return heights[0];
        }

        int res = 0;
        Deque<Integer> stack = new ArrayDeque<>(len);
        for (int i = 0; i < len; i++) {
            // 这个 while 很关键,因为有可能不止一个柱形的最大宽度可以被计算出来

            // 循环入栈清空栈中元素
            while (!stack.isEmpty() && heights[i] < heights[stack.peekLast()]) {
                // 暂时存储高度
                int curHeight = heights[stack.pollLast()];
                // 要是前后两个高度相等也出栈
                while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
                    stack.pollLast();
                }

                int width;
                if (stack.isEmpty()) {
                    //栈空说明直到最开始的一个柱形都能构成矩形
                    width = i;
                } 
                else {
                    //一般情况,在于下一个栈内元素之间都能构成矩形
                    width = i - stack.peekLast() - 1;
                }
                res = Math.max(res, curHeight * width);
            }
            stack.addLast(i);
        }

        // 如果站内没有清空,那么就循环弹出
        while (!stack.isEmpty()) {
            // 操作和上面一样,但是i变成了len,因为遍历已经结束了
            int curHeight = heights[stack.pollLast()];
            while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
                stack.pollLast();
            }
            int width;
            if (stack.isEmpty()) width = len;
            else width = len - stack.peekLast() - 1;
            res = Math.max(res, curHeight * width);
        }
        return res;
    }
}

image.png


解法三:单调栈+哨兵

public class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0) return 0;
        if (len == 1) return heights[0];

        int res = 0;
        int[] newHeights = new int[len + 2];
        // 哨兵一号
        newHeights[0] = 0;
        System.arraycopy(heights, 0, newHeights, 1, len);
        // 哨兵2号
        newHeights[len + 1] = 0;
        len += 2;
        //数据迁移
        heights = newHeights;

        Deque<Integer> stack = new ArrayDeque<>(len);
        // 将首位哨兵优先入栈,避免栈空判断
        stack.addLast(0);
        
        for (int i = 1; i < len; i++) {
            while (heights[i] < heights[stack.peekLast()]) {
                int curHeight = heights[stack.pollLast()];
                int curWidth = i - stack.peekLast() - 1;
                res = Math.max(res, curHeight * curWidth);
            }
            stack.addLast(i);
        }
        return res;
    }
}

image.png


解法四:动态规划

class Solution {
    public int largestRectangleArea(int[] heights) {
        return maxArea(heights, 0, heights.length - 1);
    }

    public int maxArea(int[] heights, int left, int right) {
        if (left > right){
            return 0;
        }
        if (left == right){
            return heights[left];
        }

        int minIndex = left;
        int minHeight = heights[left];
        // 判断是否单调递增,且记录最小高度
        boolean ordered = true;
        for (int i = left + 1; i <= right; i++) {
            if (ordered) {
                if (heights[i] >= heights[i - 1]) {
                    continue;
                } else {
                    ordered = false;
                }
            }
            if (heights[i] < minHeight) {
                minIndex = i;
                minHeight = heights[i];
            }
        }
        // 单调递增,则可以直接计算当前能够计算的最大面积
        if (ordered) {
            int width = right - left + 1;
            int maxArea = 0;
            for (int i = left; i <= right; i++) {
                maxArea = Math.max(maxArea, width * heights[i]);
                width--;
            }
            return maxArea;
        }
        // 无序,三个中求最大:最大宽度*最小高度;最矮柱左边能形成的最大面积;最矮柱右边能形成的最大面积
        return Math.max(
            minHeight * (right - left + 1), Math.max(
                maxArea(heights, left, minIndex - 1),
                maxArea(heights, minIndex + 1, right)
            )
        );
    }
}

image.png

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值