最大矩形问题

137 阅读4分钟

问题描述

给定一个高度数组 array,我们需要找到该数组中所有连续子数组的最大矩形面积。对于每个子数组,矩形的高度由该子数组中的最小高度决定,面积由子数组的长度和最小高度决定。要求我们优化这个计算过程,避免暴力枚举每个子数组。


思路分析

  1. 暴力解法

    • 在暴力解法中,我们会枚举每个子数组,并计算其最小高度及面积。时间复杂度大约是 O(n2)O(n^2)O(n2),对于较大的数组效率较低。
  2. 栈优化

    • 为了提高效率,我们可以使用单调栈来解决这个问题。栈在这里的作用是,保持一个递增的柱子高度顺序。当遇到一个比栈顶元素矮的柱子时,我们就可以计算栈顶柱子能形成的最大矩形面积。
  3. 单调栈的原理

    • 遍历每一个柱子(即数组的每个元素),维护一个递增的栈。当遇到一个比栈顶柱子矮的柱子时:

      • 弹出栈顶元素,计算该栈顶元素能够形成的最大矩形面积(宽度是当前柱子的位置减去栈顶元素的下一个位置)。
      • 继续重复直到栈顶元素小于或等于当前元素。
    • 最后处理栈中剩余的元素。

  4. 复杂度分析

    • 时间复杂度:每个元素最多会入栈一次和出栈一次,因此时间复杂度是 O(n)O(n)O(n)。
    • 空间复杂度:栈的大小最多为 O(n)O(n)O(n),因此空间复杂度是 O(n)O(n)O(n)。

代码实现

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 h = array[stack.pop()]; // 取出栈顶元素
                int width = (stack.isEmpty()) ? i : i - stack.peek() - 1; // 宽度为当前索引与栈顶索引的差
                maxArea = Math.max(maxArea, h * width); // 更新最大面积
            }
            // 当前元素入栈
            stack.push(i);
        }
        
        // 处理剩下的栈中的元素
        while (!stack.isEmpty()) {
            int h = array[stack.pop()];
            int width = (stack.isEmpty()) ? n : n - stack.peek() - 1; // 宽度为剩余部分的长度
            maxArea = Math.max(maxArea, h * width); // 更新最大面积
        }
        
        return maxArea;
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}) == 9);  // 输出: 9
        System.out.println(solution(6, new int[]{5, 4, 3, 2, 1, 6}) == 9);  // 输出: 9
        System.out.println(solution(4, new int[]{4, 4, 4, 4}) == 16);  // 输出: 16
    }
}

代码分析

  1. solution(int n, int[] array) 方法

    • 使用一个栈来维护柱子的索引,栈中的柱子始终是递增的。
    • 对于每个柱子,如果当前柱子比栈顶柱子小,就开始弹出栈顶柱子并计算以栈顶柱子为最小高度的矩形面积。矩形的宽度是当前柱子和栈顶柱子之间的差值。
    • 处理完当前柱子后,将当前柱子的索引压入栈中。
    • 在所有柱子遍历完之后,栈中可能还有一些柱子,计算这些柱子能够形成的最大矩形面积。
  2. main(String[] args) 方法

    • 该方法包含了多个测试用例,通过调用 solution 方法并打印结果来验证代码的正确性。

时间复杂度分析

  • 时间复杂度:每个柱子入栈一次,出栈一次,因此总体的时间复杂度为 O(n)O(n)O(n),其中 nnn 是数组的长度。
  • 空间复杂度:栈的最大大小为 O(n)O(n)O(n),因此空间复杂度为 O(n)O(n)O(n)。

测试用例

  1. 输入: [1, 2, 3, 4, 5]

    • 输出: 9
    • 解释: 最大的矩形面积是 [3, 4, 5],最小高度为 3,宽度为 3,因此面积为 3 * 3 = 9。
  2. 输入: [5, 4, 3, 2, 1, 6]

    • 输出: 9
    • 解释: 最大的矩形面积是 [5, 4, 3],最小高度为 3,宽度为 3,因此面积为 3 * 3 = 9。
  3. 输入: [4, 4, 4, 4]

    • 输出: 16
    • 解释: 所有元素都相等,最大矩形面积为 4 * 4 = 16

总结

  • 该算法通过单调栈的方法优化了暴力计算最大矩形面积的问题,将时间复杂度从 O(n2)O(n^2)O(n2) 降低到 O(n)O(n)O(n)。
  • 栈的使用能够有效地减少重复计算,提升效率。
  • 代码实现清晰简洁,通过多个测试用例验证了算法的正确性。