给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int largestRectangleArea(int[] heights) {
// return largestRetangleAreaI(heights);
// return largestRectangleAreaII(heights);
// return largestRectangleAreaIII(heights);
return largestRectangleAreaIII(heights);
}
//1.首先找到i位置最大面积
//2.以i为中心,向左找第一个小于heights[i]的位置leftLessMin;向右找第一个小于heights[i]的位置rightLessMin
//3.最大面积为heights[i] * (rightLessMin - leftLessMin - 1)
//如何找到rightLessMin和leftLessMin?
//方法一:暴力法,时间复杂度O(n^2)
//左右移动,遍历
private int largestRetangleAreaI(int[] heights) {
int result = 0;
int len = heights.length;
for (int i = 0; i < len; i++) {
int left = i, right = i;
while (left >= 0 && heights[left] >= heights[i]) left--;
while (right < len && heights[right] >= heights[i]) right++;
result = Math.max(result, (right - left - 1) * heights[i]);
}
return result;
}
//方法二:基于方法一优化
//定义一个数组leftLessMin[]保存各自的左边第一个小的柱子
//第一个柱子前没有柱子,赋值为-1 leftLessMin[0] = -1
//当前柱子i比上一柱子小的时候
//由于之前已经求出上一柱子的leftLessMin[i - 1],也就是第一个比上个柱子小的柱子
//所以可以直接从leftLessMin[i - 1]开始比较,因为从leftLessMin[i - 1] + 1到i - 1都是比当前柱子i高,可以直接跳过
private int largestRectangleAreaII(int[] heights) {
if (heights.length == 0) return 0;
int[] leftLessMin = new int[heights.length];
leftLessMin[0] = -1;
for (int i = 1; i < heights.length; i++) {
int l = i - 1;
//当前柱子更小
while (l >= 0 && heights[l] >= heights[i]) {
l = leftLessMin[l];
}
leftLessMin[i] = l;
}
int[] rightLessMin = new int[heights.length];
//最后一个柱子的右边实际没有柱子,赋值为数组长度
rightLessMin[heights.length - 1] = heights.length;
for (int i = heights.length - 2; i >= 0; i--) {
int r = i + 1;
while (r <= heights.length - 1 && heights[r] > heights[i]) {
r = rightLessMin[r];
}
rightLessMin[i] = r;
}
//求面积
int result = 0;
for (int i = 0; i < heights.length; i++) {
result = Math.max(result, (rightLessMin[i] - leftLessMin[i] - 1) * heights[i]);
}
return result;
}
//方法三:基于方法二的思想使用栈
//1.当前栈为空,或者当前柱子大于栈顶元素高度,将当前柱子的下标入栈
//2.当前柱子高度小于栈顶元素,将栈顶出栈,当做当前要求面积的柱子
//3.右边第一个小于当前柱子的下标为当前在遍历的柱子,左边第一个小于当前柱子的下标为当前新的栈顶
//4.遍历结束后,如果栈没有空,依次出栈,出栈元素当做要求面积的柱子,然后依次计算面积
private int largestRectangleAreaIII(int[] heights) {
int result = 0, p = 0;
Stack stack = new Stack<>();
while (p < heights.length) {
//栈为空,入栈
if (stack.isEmpty()) {
stack.push(p++);
} else {
//判断栈顶元素与入栈元素高度大小
int top = stack.peek();
//入栈元素大于栈顶元素,入栈
if (heights[p] >= heights[top]) {
stack.push(p++);
} else {
//否则,栈顶元素出栈
int height = heights[stack.pop()];
//左边第一个小于当前柱子的下标为新栈顶元素
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右边第一个小于当前柱子的下标为当前正遍历柱子
int rightLessMin = p;
//计算面积
result = Math.max(result, (rightLessMin - leftLessMin - 1) * height);
}
}
}
//边界元素
while (!stack.isEmpty()) {
//依次出栈,出栈元素为当前要求面积的柱子
int height = heights[stack.pop()];
//左边第一个小于当前柱子的下标
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右边没有小于当前高度的柱子,赋值为数组的长度便于计算
int rightLessMin = heights.length;
result = Math.max(result, (rightLessMin - leftLessMin - 1) * height);
}
return result;
}
//方法四:分治法,时间复杂度 平均O(nlogn),最坏o(n^2)
//问题可分为三种情况:
//1.找出最短柱子,矩形的宽尽可能往两边延伸
//2.在最短柱子的左边找出最大面积
//3.在最短柱子的右边找出最大面积
//举例[2,1,5,6,2,3]
//最短柱子为1,宽度为6,面积1*6=6
//对高度为1的左右两边采取同样的过程
//1的左边,形成面积2*1=2;1的右边,最短柱子2,形成面积2*4=8,以此类推,2的左边形成5*2=10,6*1=6,2的右边3*1=3
//最终得到面积最大值5*2=10
private int largestRectangleAreaIIII(int[] heights) {
return calculateMaxArea(heights, 0, heights.length - 1);
}
private int calculateMaxArea(int[] heights, int left, int right) {
if (left > right) return 0;
int minIndex = left;
//求出最短柱子
for (int i = left; i <= right; i++) {
if (heights[i] < heights[minIndex]) {
minIndex = i;
}
}
return Math.max(heights[minIndex] * (right - left + 1),
Math.max(calculateMaxArea(heights, left, minIndex - 1), calculateMaxArea(heights, minIndex + 1, right)));
}
}