问题分析
小S需要解决的问题是:给定一个数组 h1, h2, ..., hN,数组的每个元素代表某种高度。小S希望找出对于任意 k 个相邻元素,它们所能形成的最大矩形面积。具体来说,对于 k 个相邻的元素,定义其矩形的最大面积为:
即,R(k) 的值为这 k 个相邻元素中的最小值乘以 k。现在,小S希望帮他找出对于任意 k,R(k) 的最大值。
解法分析
可以总结出以下几种解法:
-
暴力法:
- 遍历所有可能的
k值,对于每个k,计算所有k个相邻元素的最小值,并计算其矩形面积。 - 时间复杂度为 ,空间复杂度为 。
- 遍历所有可能的
-
单调栈法:
- 使用单调栈来计算每个元素左边和右边第一个比它小的元素的下标,从而确定以该元素为最小值的矩形面积。
- 时间复杂度为 ,空间复杂度为 。
-
动态规划法:
- 通过动态规划的方式,记录每个元素左边和右边第一个比它小的元素的下标,从而计算最大矩形面积。
- 时间复杂度为 ,空间复杂度为 。
代码实现
以下是基于单调栈法的Java代码实现:
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];
// 初始化L和R数组
for (int i = 0; i < n; i++) {
L[i] = i;
R[i] = i;
}
// 单调栈计算L(i),从左到右
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.empty() && array[stack.peek()] >= array[i]) {
stack.pop();
}
L[i] = stack.empty() ? 0 : stack.peek() + 1;
stack.push(i);
}
// 清空栈用于计算R(i)
stack.clear();
// 单调栈计算R(i),从右到左
for (int i = n - 1; i >= 0; i--) {
while (!stack.empty() && array[stack.peek()] >= array[i]) {
stack.pop();
}
R[i] = stack.empty() ? n - 1 : stack.peek() - 1;
stack.push(i);
}
// 计算 MAX(i) = (R(i) - L(i) + 1) * array[i] 并找出最大值
int maxArea = 0;
for (int i = 0; i < n; i++) {
maxArea = Math.max(maxArea, (R[i] - L[i] + 1) * array[i]);
}
return maxArea;
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(5, new int[]{5, 4, 3, 4, 5})); // 输出: 8
System.out.println(solution(6, new int[]{2, 1, 4, 3, 6, 5})); // 输出: 15
System.out.println(solution(7, new int[]{1, 2, 3, 4, 5, 6, 7})); // 输出: 0
}
}
学习感悟
-
算法复杂度分析:
- 暴力法虽然简单,但时间复杂度较高,不适合大规模数据。
- 单调栈法和动态规划法在时间复杂度上都有显著优势,适合处理大规模数据。
-
数据结构的应用:
- 单调栈是一种非常强大的数据结构,适用于解决许多与区间最值相关的问题。
- 动态规划则通过记录中间结果,避免了重复计算,提高了效率。
-
代码优化:
- 在实际编程中,合理使用数据结构和算法可以大大提升代码的执行效率。
- 例如,通过预处理数组,减少不必要的计算,可以进一步优化代码性能。
-
问题抽象与解决:
- 将实际问题抽象为数学模型,并找到合适的算法来解决,是编程的核心能力。
- 在解决复杂问题时,分解问题、逐步求解是常用的方法。
通过以上分析和代码实现,我们可以看到如何利用单调栈来高效地解决最大矩形面积问题,并从中学习到算法设计和数据结构应用的重要性。