问题描述
小S最近在分析一个数组 h1,h2,...,hN,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意 k 个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于 k 个相邻的元素,我们定义其矩形的最大面积为:
R(k)=k×min(h[i],h[i+1],...,h[i+k−1])
即R(k) 的值为这 k 个相邻元素中的最小值乘以 k。现在,小S希望你能帮他找出对于任意 k,R(k) 的最大值。
问题链接:最大矩形面积问题 - MarsCode
测试样例
样例1:
输入:
n = 5, array = [1, 2, 3, 4, 5]
输出:9
样例2:
输入:
n = 6, array = [5, 4, 3, 2, 1, 6]
输出:9
样例3:
输入:
n = 4, array = [4, 4, 4, 4]
输出:16
解题思路
问题理解
我们需要在数组中找到 k 个相邻元素,使得所能形成的最大矩形面积。而矩形的面积是这 k 个元素中的最小值乘以 k。我们可以设每个柱子的高度k区间内为最小值,这样问题就可以转化为以这个柱子为中心来确定左右边界,左右边界分别为第左侧第一个小于该高度的位置和右侧第一个小于该高度的位置,计算的时候舍去边界,只需计算边界内柱子数k * 该柱子高度。
数据结构选择
对于区间维护问题,一般都可以使用单调栈作为解题的突破口。单调栈可以帮助我们快速找到每个元素作为最小值时,左右两边第一个比它小的元素的位置。
-
左边界:栈中维护高度的递增顺序,当遇到一个小于栈顶的高度时,出栈,从而找到当前高度左边第一个比它小的位置。
-
右边界:以类似方式,从右到左遍历数组,找右边界。
代码解释
-
初始化:
l
和r
数组用来存储每个元素的左右边界。s
是一个单调栈,用来维护计算边界。
int[] l = new int[n]; int[] r = new int[n]; Stack<Integer> s = new Stack<>();
-
计算
l
数组和r
数组:以
l
数组为例- 遍历数组,对于每个元素,如果栈顶元素大于等于当前元素,则弹出栈顶元素。
- 当前元素的
l
值为栈顶元素的位置,如果栈为空则为-1
。 - 将当前元素的位置压入栈中。
for (int i = 0; i < n; i++) { while (!s.isEmpty() && array[s.peek()] >= array[i]) { s.pop(); } l[i] = s.isEmpty() ? -1 : s.peek(); s.push(i); }
-
计算最大面积:
- 对于每个元素,计算以它为最小值时的最大矩形面积,即
array[i] * (right[i] - left[i] - 1)
。 - 遍历元素更新最大面积。
int res = 0; for (int i = 0; i < n; i++) { int k = r[i] - l[i] - 1; res = Math.max(res, array[i] * k); }
- 对于每个元素,计算以它为最小值时的最大矩形面积,即
完整代码
import java.util.Stack;
public class Main {
public static int solution(int n, int[] array) {
// 初始化单调栈和左右边界数组
int[] l = new int[n];
int[] r = new int[n];
Stack<Integer> s = new Stack<>();
// 计算l数组
for (int i = 0; i < n; i++) {
while (!s.isEmpty() && array[s.peek()] >= array[i]) {
s.pop();
}
l[i] = s.isEmpty() ? -1 : s.peek();
s.push(i);
}
// 计算r数组
s.clear();
for (int i = n - 1; i >= 0; i--) {
while (!s.isEmpty() && array[s.peek()] >= array[i]) {
s.pop();
}
r[i] = s.isEmpty() ? n : s.peek();
s.push(i);
}
// 最大面积
int res = 0;
for (int i = 0; i < n; i++) {
int k = r[i] - l[i] - 1;
res = Math.max(res, array[i] * k);
}
return res;
}
public static void main(String[] args) {
System.out.println(solution(5, new int[] { 1, 2, 3, 4, 5 }) == 9);
System.out.println(solution(6, new int[] { 5, 4, 3, 2, 1, 6 }) == 9);
System.out.println(solution(4, new int[] { 4, 4, 4, 4 }) == 16);
}
}
时间复杂度分析
O(n),因为对每个元素而言,最多出入栈一次;