问题描述
小S最近在分析一个数组 h1,h2,...,h_n h1,h2,...,h_n,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意 k 个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于k个相邻的元素,我们定义其矩形的最大面积为: R(k)=k×min(h[i],h[i+1],...,h[i+k−1])
即,R(k) 的值为这 k 个相邻元素中的最小值乘以 k。现在,小S希望你能帮他找出对于任意k,R(k) 的最大值。
思路
假设最大矩形是由i-j之间的数据组成的,那么矩形的高度就为min(h[i],h[i+1],...h[j]),矩形的长度为j-i+1, 可以注意到,高度一定是h中的元素,那么产生思路就是判断每个h[i]为最大高度时,能生成的最大矩形面积是多少,然后取最大值。
这种思路,需要寻找每个h[i]两边第一个低于h[i]的位置,此时得到的为当前位置作为高度时的最大宽度,也就是生成的最大矩形面积。
最简单方法是暴力法,每次往两边遍历寻找,显然时间复杂度为O(n^2),一般是要超时的
为了解决这个问题,我们可以使用一个更高效的算法找左右最小值,其时间复杂度为O(n)。这个算法利用了单调栈的性质来找到每个高度值作为矩形高度时的最大宽度。
定义两个数组left和right,记录位置i左右两边第一个小值的索引
先从左到右遍历h,对每个元素,当栈不为空且 h[i] 小于栈顶元素对应的高度时,弹出栈顶元素,直到栈为空或 h[i] 大于等于栈顶元素对应的高度。
弹出的元素索引是 h[i] 左边第一个低于 h[i] 的元素的索引,将其存储在 left[i] 中。
将当前索引 i 入栈。这样就构建了left数组。
right数组改成从右开始遍历,思路同上
计算最大矩形面积时就直接利用left和right两个数组计算矩阵宽度,为right[i]-left[i]-1,然后不断更新maxArea可得结果。
这个算法的时间复杂度为O(n),因为每个元素最多被压栈和弹栈一次。这种方法有效地利用了单调栈的性质来快速找到每个高度值作为矩形高度时的最大宽度,从而避免了暴力法中的重复计算。
算法的伪代码实现如下:
function calculateMaxArea(h):
n = length(h)
left = [-1] * n
right = [n] * n
stack = []
// 构建左侧索引数组
for i from 0 to n-1:
while stack != [] and h[i] < h[stack.peek()]:
stack.pop()
if stack != []:
left[i] = stack.peek()
stack.push(i)
// 构建右侧索引数组
for i from n-1 to 0 step -1:
while stack != [] and h[i] < h[stack.peek()]:
stack.pop()
if stack != []:
right[i] = stack.peek()
stack.push(i)
maxArea = 0
for i from 0 to n-1:
width = right[i] - left[i] - 1
maxArea = max(maxArea, width * h[i])
return maxArea