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.
Example:
Input: [2,1,5,6,2,3]
Output: 10
思路
此题可理解为,找出每个小矩形所能扩展的最大矩形的面积
-
暴力解法
注意到每个小矩形能扩展的最大矩形的高就是当前小矩形的高,而宽的长度是需要我们寻找的,这个宽需要满足的条件为:宽的左右边界所对应的小矩形的高度应不低于当前小矩形的高度。
因此可以在遍历数组中每个元素时,分别向左右两端遍历,得到宽进而求出矩形面积,所有面积的最大值即为所求。
时间复杂度: O(n2)
空间复杂度: O(1) -
单调栈解法
注意到暴力解法是从前往后遍历每个元素找解的方法,由于每次遍历的时候,都要找左右边界,以至于做了很多重复的运算。注意到找左右边界实际就是要找到左右两边最近的不小于当前元素的元素。因此我们可以考虑如下场景:
(1)我们假设当前元素是A[i],每次遍历的时候,判断A[i]和A[i+1],如果A[i] < A[i+1],则把A[i]保存下来,继续遍历下一个;如果A[i] > A[i+1],那么A[i+1]就是A[i]的右边界,而左边界就是我们保存下来的离A[i]最近的那个元素;
(2)找到左边界和右边界后,我们就可以把当前元素的最大矩形计算出来,然后当前元素就可以从保存的元素中除去,此时我们需要考虑,此种场景的适用条件是什么?适用条件就是保存的元素必须是从小到大排列的,也就是我们需要维护一个递增序列;
(3)经过以上分析,我们可以意识到我们维护的递增序列其实就是一个单调栈,我们每次比较A[i]和A[i+1]时,实际是比较的栈顶元素与当前元素,如果栈顶元素大于当前元素,则开始出栈操作。出栈后,出栈元素的值就是最大矩形的高,当前元素与当前栈顶元素的下标的差值减一就是最大矩形的宽。
(4)注意到上述的一般场景对于首元素和尾元素是不适用的,此时我们可以增加首尾两个哨兵,来将这两个特殊场景一般化处理。
时间复杂度: O(n)
空间复杂度: O(n)
Java实现
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) {
return 0;
}
int length = heights.length;
if (length == 1) {
return heights[0];
}
// 将数组前后加上哨兵,方便首尾特殊情况的处理
int[] arr = new int[length+2];
for (int i = 0; i < length; i++) {
arr[i + 1] = heights[i];
}
length += 2;
heights = arr;
// 定义面积
int area = 0;
// 定义栈
Deque<Integer> stack = new ArrayDeque<>();
// 首哨兵先入栈
stack.push(0);
// 首哨兵不参加遍历
for (int i = 1; i < length; i++) {
// 由于设置了哨兵,栈永远都不会空
// 栈顶元素 > 当前元素,进行出栈操作
while (heights[stack.peek()] > heights[i]) {
// 计算高,宽和面积
int height = heights[stack.pop()];
int width = i - stack.peek() - 1;
area = Math.max(area, height * width);
}
// 跳出循环后意味着:栈顶元素 <= 当前元素,进行入栈操作
stack.push(i);
}
return area;
}
}
Python实现
class Solution(object):
def largestRectangleArea(self, heights):
"""
:type heights: List[int]
:rtype: int
"""
if heights == None or len(heights) == 0:
return 0
length = len(heights)
if length == 1:
return heights[0]
# 设置首尾哨兵
heights.insert(0, 0)
heights.append(0)
length += 2
# 定义面积
area = 0
# 定义栈
stack = []
# 首哨兵入栈
stack.append(0)
# 首哨兵不参加遍历
for i in range(1, length):
# 由于设置了哨兵,栈永远都不会空
# 栈顶元素 > 当前元素,进行出栈操作
while heights[stack[-1]] > heights[i]:
height = heights[stack.pop()]
width = i - stack[-1] - 1
area = max(area, height * width)
# 跳出循环后意味着:栈顶元素 <= 当前元素,进行入栈操作
stack.append(i)
return area
Scala实现
import scala.collection.mutable.Stack
import scala.math._
object Solution {
def largestRectangleArea(heights: Array[Int]): Int = {
if (heights == null || heights.length == 0) {
return 0
}
var length = heights.length
if (length == 1) {
return heights(0)
}
// 对原始数组设置首尾哨兵
val heights2 = new Array[Int](length + 2)
for (i <- 0 until length) {
heights2(i+1) = heights(i)
}
length += 2
// 定义面积
var area = 0
// 定义栈
val stack = new Stack[Int]()
// 首哨兵先入栈
stack.push(0)
// 首哨兵不参加遍历
for (i <- 1 until length) {
// 由于设置了哨兵,栈永远都不会空
// 栈顶元素 > 当前元素,进行出栈操作
while (heights2(stack.top) > heights2(i)) {
val height = heights2(stack.pop())
val width = i - stack.top - 1
area = max(area, height * width)
}
// 跳出循环后意味着:栈顶元素 <= 当前元素,进行入栈操作
stack.push(i)
}
return area
}
}