​welcome to my blog​

LeetCode Top 100 Liked Questions 84. Largest Rectangle in Histogram (Java版; Hard)

题目描述

Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, 
find the area of largest rectangle in the histogram.

LeetCode Top 100 Liked Questions 84. Largest Rectangle in Histogram (Java版; Hard)_Stack

Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3]

LeetCode Top 100 Liked Questions 84. Largest Rectangle in Histogram (Java版; Hard)_LeetCode_02

The largest rectangle is shown in the shaded area, which has area = 10 unit.

Example:

Input: [2,1,5,6,2,3]
Output: 10
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
if(n==0){
return 0;
}
Stack<Integer> s = new Stack<>();
s.push(-1);
int max = 0;
//
for(int i=0; i<n; i++){
while(s.size()!=1 && heights[i] < heights[s.peek()]){
int j = s.pop();
max = Math.max(max, (i-s.peek()-1)*heights[j]);
}
s.push(i);
}
//栈顶元素一定是heights[n-1]
//
while(s.size()!=1){
int j = s.pop();
max = Math.max(max, (n-s.peek()-1)*heights[j]);
}
return max;
}
}

第一次做, 分治算法; 核心: 最大面积矩形是以下三种情况中的某一种, (1)以最矮柱子为高,向两端扩展的矩形, (2)最矮柱子左边的最大矩形, (3)最矮柱子右边的最大矩形; 我用分治法的时候考虑了这么一种情况, 如果数组只有两个元素, 最小值时靠右的那个, 再次递归时, 有左部分, 但是没有右部分了, 所以我提前判断了是否有左右部分, 不过也可以直接使用if(left>right)这个递归终止条件, 不用提前判断是否有左右部分; 844ms

复杂度分析

时间复杂度:平均开销:O(nlogn); 最坏情况:O(n^2)

如果数组中的数字是有序的,分治算法将没有任何优化效果。

空间复杂度:O(n)。最坏情况下递归需要 O(n) 的空间。
class Solution {
public int largestRectangleArea(int[] heights) {
if(heights==null || heights.length==0)
return 0;
int res = Core(heights, 0, heights.length-1);
return res;
}
public int Core(int[] heights, int left, int right){
//base case
if(left==right)
return heights[left];
int minIndex=left;
for(int i=left+1; i<=right; i++)
minIndex = heights[i] < heights[minIndex] ? i : minIndex;
int curr = (right - left + 1) * heights[minIndex];
int leftArea=0, rightArea = 0;
//如果有左部分的话
if(minIndex-1>=left)
leftArea = Core(heights, left, minIndex-1);
//如果有右部分的话
if(minIndex+1<=right)
rightArea = Core(heights, minIndex+1, right);
return Math.max(Math.max(curr, leftArea), rightArea);
}
}

第一次做, 单调栈:栈底到栈顶递增; 遍历阶段, 清算阶段; 栈中存索引; 初始时栈中压入-1, 为的是计算栈中倒数第二个元素对应的矩形面积; 以弹出的元素对应的高度作为矩形的高度进行计算; 注意遍历阶段和清算阶段面积公式的差别; 和LeetCode32(Hard)很像; 29ms

/*
这是道好题, 和LC32的栈解法有类似的地方, 这两道题的栈解法都需要先往栈中压入一个参考, 方便计算宽度;
区别是LC32会更新参考值, 本题一直以-1作为参考值
实际上也是一种单调栈结构, 具有遍历阶段和清算阶段这两个阶段
*/
import java.util.Stack;

class Solution {
public int largestRectangleArea(int[] heights) {
if(heights==null || heights.length==0)
return 0;
//栈中压入的是索引
Stack<Integer> s = new Stack<>();
//压入参考值, 方便计算宽度
s.push(-1);
//单调栈:栈底到栈顶递增; 包含重复值, 重复值正常入栈就行
//遍历阶段
int res=0, curr, index;
for(int i=0; i<heights.length; i++){
if(s.size()==1)
s.push(i);
else{
if(heights[i] >= heights[s.peek()]){
s.push(i);
}
else{
while(s.size()!=1 && heights[s.peek()] > heights[i]){
//计算以当前柱子高度作为高的矩形的面积
index = s.pop();
curr = (i - s.peek() - 1) * heights[index];
res = Math.max(res, curr);
}
s.push(i);
}
}
}
//清算阶段
//该阶段, 栈中每个索引都满足: 从自己到末尾heights.length-1的范围中, 自己对应的高度最低, 发现这一点非常重要!!
while(s.size()!=1){
index = s.pop();
/*
细节: 括号内减去的是s.peek(), 这里体现了最开始压入-1的作用
具体来说, 输入的高度是{2,1,5,6,2,3},清算阶段,栈中还剩(-1,1,4,5)
弹出5的时候, curr = (heights.length-1 - 4)*heights[5]
弹出4的时候, curr = (heights.length-1 - 1)*heights[4]
弹出1的时候, curr = (heights.length-1 - (-1))*heights[1]
清算阶段,栈中倒数第二个索引对应的高度是所有高度中的最小值, 对应的面积是数组的长度乘栈中倒数第二个元素对应的高度
*/
curr = (heights.length - 1 - s.peek()) * heights[index];
res = Math.max(res, curr);
}
return res;
}
}

题解, 单调栈解法, 写的比我简洁很多

public class Solution {
public int largestRectangleArea(int[] heights) {
Stack < Integer > stack = new Stack < > ();
stack.push(-1);
int maxarea = 0;
for (int i = 0; i < heights.length; ++i) {
while (stack.peek() != -1 && heights[stack.peek()] >= heights[i])
maxarea = Math.max(maxarea, heights[stack.pop()] * (i - stack.peek() - 1));
stack.push(i);
}
while (stack.peek() != -1)
maxarea = Math.max(maxarea, heights[stack.pop()] * (heights.length - stack.peek() -1));
return maxarea;
}
}

题解, 分支算法, 主要注意递归终止条件; 我用分治法的时候考虑了这么一种情况, 如果数组只有两个元素, 最小值时靠右的那个, 再次递归时, 有左部分, 但是没有右部分了, 所以我提前判断了是否有左右部分, 不过也可以直接使用if(left>right)这个递归终止条件, 不用提前判断是否有左右部分

public class Solution {
public int calculateArea(int[] heights, int start, int end) {
if (start > end)
return 0;
int minindex = start;
for (int i = start; i <= end; i++)
if (heights[minindex] > heights[i])
minindex = i;
return Math.max(heights[minindex] * (end - start + 1), Math.max(calculateArea(heights, start, minindex - 1), calculateArea(heights, minindex + 1, end)));
}
public int largestRectangleArea(int[] heights) {
return calculateArea(heights, 0, heights.length - 1);
}
}