问题描述
小S最近在分析一个数组h1,h2,…,hN,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意k个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于k个相邻的元素,我们定义其矩形的最大面积为:
R(k) = k x min(h[i], h[i+1], ... , h[i+k-1])
即,R(k)的值为这k个相邻元素中的最小值乘以k。现在,小S希望你能帮他找出对于任意k,R(k)的最大值。
测试样例
样例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个相邻元素能够形成的最大矩形面积。通过题目中的公式:
[R(k) = k × min(h[i], h[i+1], ..., h[i+k-1])]
可以看到,问题的难点在于如何高效地找到这些子数组的最小值,以及利用它计算最大面积。
关键点
- 矩形面积:每一组相邻的元素形成的矩形高度是这组元素中的最小值,宽度是元素数量
k。 - 效率:暴力解法需要枚举所有可能的子数组,并计算其最小值,这会导致时间复杂度为
O(N^2),在数组较大时不可接受。
为了解决这个问题,我们可以利用 单调栈 的思想来优化。
思路解析
单调栈是一种常用的数据结构,用于在遍历数组的同时找到每个元素的“最近的更小值”或“最近的更大值”。在这个问题中,我们利用单调栈来找到每个高度作为矩形最小值时可以扩展的最远范围。
思路步骤
-
理解矩形扩展的范围:
- 对于数组中的任意高度
array[i],它可以作为最小高度的矩形范围是从第一个左边比它小的元素之后,到第一个右边比它小的元素之前。 - 这些范围的宽度可以通过单调栈快速计算。
- 对于数组中的任意高度
-
单调栈的具体操作:
- 从左到右遍历数组,维护一个递增的栈:
- 栈中存储的是数组的索引。
- 每次遇到比栈顶元素小的值时,就可以计算以栈顶高度为矩形最小高度的面积。
- 在栈清空时,将剩余的元素也计算一次。
- 从左到右遍历数组,维护一个递增的栈:
-
计算面积:
- 宽度公式:
- 当前元素为
array[i],栈中弹出元素索引为top:- 左边界为:栈顶下一个元素(若栈为空,则为
0)。 - 右边界为:当前索引
i。
- 左边界为:栈顶下一个元素(若栈为空,则为
- 宽度为:
i - left - 1。
- 当前元素为
- 宽度公式:
-
最终最大面积:
- 遍历过程中动态维护最大面积值。
图解
假设数组为[5, 4, 3, 2, 1, 6],逐步演示单调栈的操作:
-
初始状态: 栈为空,最大面积为0。
-
遍历过程中:
- 元素
5入栈。 - 元素
4比5小,弹出5,计算面积为5 x 1 = 5,入栈4。 - 元素
3比4小,弹出4,计算面积为4 x 2 = 8,入栈3。 - 按此逻辑,依次弹出并计算面积。
- 元素
-
结束后: 栈中剩余的元素需要依次处理。
代码实现
import java.util.Stack;
public class Main {
public static int solution(int n, int[] array) {
// 单调栈
Stack<Integer> stack = new Stack<>();
int maxArea = 0;
// 遍历数组
for (int i = 0; i < n; i++) {
// 栈中保持递增
while (!stack.isEmpty() && array[stack.peek()] >= array[i]) {
int height = array[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
// 处理剩余元素
while (!stack.isEmpty()) {
int height = array[stack.pop()];
int width = stack.isEmpty() ? n : n - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
return maxArea;
}
public static void main(String[] args) {
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5})); // Output: 9
System.out.println(solution(6, new int[]{5, 4, 3, 2, 1, 6})); // Output: 9
System.out.println(solution(4, new int[]{4, 4, 4, 4})); // Output: 16
}
}
代码解析
-
stack.push(i):- 当前索引入栈,表示该高度可能是未来矩形的一部分。
-
stack.pop():- 当新元素小于栈顶元素时,表示以栈顶高度为矩形最小高度的范围结束。
-
计算面积:
height * width,动态更新最大面积。
-
剩余元素处理:
- 栈未清空时,表示这些高度的右边界为数组末尾。
时间复杂度
- 遍历数组 + 每个元素最多入栈/出栈一次:O(N)
空间复杂度
- 额外栈的空间:O(N)
测试用例
- 单调递增:
[1, 2, 3, 4, 5]→ 输出:9 - 单调递减:
[5, 4, 3, 2, 1]→ 输出:9 - 全部相同:
[4, 4, 4, 4]→ 输出:16 - 复杂情况:
[2, 1, 5, 6, 2, 3]→ 输出:10