leetcode84- 柱状图中最大的矩形(三种思路:暴力,单调栈+哨兵(详解),分治)
介绍
标签:贪心
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大佬的题解以及官方题解的视频
解法一:暴力向两边搜索
对该柱子两边进行遍历,寻找以当前高度可以取得的最大面积,看一眼应该就懂了
解法二:单调栈
构建一个递增的单调栈,因为:
- 如果后一个柱高度比目前柱更高,说明矩形面积还有增加的可能性
- 如果后一个柱高度比目前柱更矮,说明已经没有增长空间了,可以出栈计算能形成的最大面积
画图演示
-
heights[0] 因为是第一个所以直接入栈
-
遍历到heights[1]比heights[0]更矮,计算可得heights[0]的最大面积,然后将heights[1]入栈
-
遍历到heights[2],发现heights[1]更矮,因此heights[2]直接入栈
-
遍历到heights[3],发现heights[2]更矮,因此heights[3]也直接入栈
-
遍历到heights[4],发现当前栈顶元素heights[3]更高,则将其出栈,并计算其最大面积
-
继续比较会发现当前栈顶元素heights[2]还是比heights[4]大,继续出栈并计算最大面积
-
遍历到最后一个heights[5],发现heights[4]更矮,heights[4]入栈
-
遍历已经到最末尾了,将heights[5]也入栈,准备将栈内元素全部清空了
宽度计算:
宽度计算全部发生在出栈的情况下,并且有两个情况:
- 栈内没有其他元素了,那么一直到heights[0]都可以在当前高度下形成矩形
- 栈内还有其他元素,那么直到次栈顶元素,这段区间都是可以形成矩形的
解法三:单调栈+哨兵
因为在解法二中考虑了栈空与结尾栈不为空的情况,导致了造成了冗余,所以就在输入数组的两端加上两个高度为0
的柱子,使之为**“哨兵”**
有了哨兵之后:
- 数组第一位的哨兵会使栈一定不会为空
- 数组最后位的哨兵会使前面全部有高度的柱出栈
从而可以保证将全部的柱能形成的高度全部计算一遍
解法四:分治,这是最快的方法
将数组分为最矮柱左边的部分与最矮柱右边的部分
然后三个中求最大
- 最大宽度*最小高度
- 最矮柱左边能形成的最大面积
- 最矮柱右边能形成的最大面积
同时对数组段是否递增进行判断,如果一旦是单调递增,那么最大的面积毫无疑问就是最矮柱高度*当前宽度
代码
解法一:暴力
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;
}
}
这么慢的吗…
解法二:单调栈
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;
}
}
解法三:单调栈+哨兵
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;
}
}
解法四:动态规划
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)
)
);
}
}