最大矩形面积问题 | 豆包MarsCode AI刷题

152 阅读4分钟

算法分析:最大矩形面积问题

问题背景

小S提出的问题是一个经典的算法问题,核心是分析数组的连续子区间,并找出这些子区间能够形成的最大矩形面积。具体而言,给定一个高度数组 hhh,我们需要计算任意 kkk 个相邻元素的矩形面积 R(k)=k×min⁡(h[i],h[i+1],...,h[i+k−1])R(k) = k \times \min(h[i], h[i+1], ..., h[i+k-1])R(k)=k×min(h[i],h[i+1],...,h[i+k−1]),并找到所有 R(k)R(k)R(k) 中的最大值。

例如,数组 h=[1,2,3,4,5]h = [1, 2, 3, 4, 5]h=[1,2,3,4,5],对于 k=3k=3k=3 时,连续的子区间有 [1,2,3],[2,3,4],[3,4,5][1,2,3], [2,3,4], [3,4,5][1,2,3],[2,3,4],[3,4,5],其最小值分别是 1, 2, 3,对应的矩形面积为 3, 6, 9。因此,最大矩形面积为 9。

暴力解法

实现思路

暴力解法的实现基于嵌套循环:

  1. 外层循环 遍历子区间长度 kkk(从 1 到 nnn)。
  2. 中层循环 遍历数组的起始位置 iii。
  3. 内层循环 遍历子区间并找出最小值 min(h[i],h[i+1],...,h[i+k−1])\text{min}(h[i], h[i+1], ..., h[i+k-1])min(h[i],h[i+1],...,h[i+k−1])。

对于每个子区间,计算矩形面积并更新最大值。

代码实现

以下是暴力解法的代码实现:

cpp
复制代码
#include <bits/stdc++.h>
using namespace std;

int solution(int n, std::vector<int> A) {
    int res = 0;

    for (int k = 1; k <= n; k++) {
        for (int i = 0; i <= n - k; i++) {
            int minnum = INT_MAX;
            for (int j = i; j < i + k; j++) {
                minnum = min(minnum, A[j]);
            }
            res = max(res, minnum * k);
        }
    }

    return res;
}

int main() {
    vector<int> A_case1 = {1, 2, 3, 4, 5};
    cout << (solution(5, A_case1) == 9) << endl;
    return 0;
}

时间复杂度分析

  1. 外层循环 O(n)O(n)O(n):遍历子区间长度 kkk。
  2. 中层循环 O(n)O(n)O(n):遍历起始位置 iii。
  3. 内层循环 O(k)O(k)O(k):遍历子区间计算最小值。

综合时间复杂度为 O(n3)O(n^3)O(n3)。对于大规模数据(如 n>104n > 10^4n>104),此方法效率低下,可能导致超时。


优化思路

核心问题

暴力解法的低效在于:

  1. 重复计算子区间的最小值。
  2. 多层循环导致较高的时间复杂度。

方法一:滑动窗口优化

对于固定长度 kkk,可以使用双端队列(deque)来维护窗口内的最小值。这种方法将寻找最小值的复杂度从 O(k)O(k)O(k) 降低到 O(1)O(1)O(1)。

关键点
  1. 双端队列中的元素始终保持从小到大排列。
  2. 窗口滑动时,动态更新双端队列的元素。
时间复杂度

滑动窗口方法的时间复杂度为 O(n2)O(n^2)O(n2),比暴力解法显著优化,但仍需进一步改进。


方法二:单调栈优化(直方图思路)

单调栈是解决矩形问题的经典方法,通过快速找到每个元素的左、右边界,直接计算以某个高度为基准的最大矩形面积。

优化思想
  1. 对于每个元素 h[i]h[i]h[i],找到左边第一个小于 h[i]h[i]h[i] 的位置和右边第一个小于 h[i]h[i]h[i] 的位置。
  2. 根据这些边界计算矩形的宽度,从而得出面积。
代码实现
cpp
复制代码
#include <bits/stdc++.h>
using namespace std;

int solution(int n, vector<int> A) {
    vector<int> left(n), right(n);
    stack<int> st;

    // 计算左边界
    for (int i = 0; i < n; i++) {
        while (!st.empty() && A[st.top()] >= A[i]) {
            st.pop();
        }
        left[i] = st.empty() ? -1 : st.top();
        st.push(i);
    }

    // 清空栈
    while (!st.empty()) {
        st.pop();
    }

    // 计算右边界
    for (int i = n - 1; i >= 0; i--) {
        while (!st.empty() && A[st.top()] >= A[i]) {
            st.pop();
        }
        right[i] = st.empty() ? n : st.top();
        st.push(i);
    }

    // 计算最大面积
    int res = 0;
    for (int i = 0; i < n; i++) {
        int width = right[i] - left[i] - 1;
        res = max(res, A[i] * width);
    }

    return res;
}

int main() {
    vector<int> A_case1 = {1, 2, 3, 4, 5};
    cout << (solution(5, A_case1) == 9) << endl;

    vector<int> A_case2 = {5, 4, 3, 2, 1};
    cout << (solution(5, A_case2) == 9) << endl;

    vector<int> A_case3 = {2, 1, 5, 6, 2, 3};
    cout << (solution(6, A_case3) == 10) << endl;

    return 0;
}
时间复杂度
  • 边界计算: O(n)O(n)O(n),每个元素最多入栈出栈一次。
  • 面积计算: O(n)O(n)O(n)。

总时间复杂度为 O(n)O(n)O(n)


总结

  1. 暴力解法适合规模较小的问题,但时间复杂度 O(n3)O(n^3)O(n3) 难以满足大规模需求。
  2. 滑动窗口优化可以将复杂度降至 O(n2)O(n^2)O(n2),适用于中等规模问题。
  3. 单调栈方法最优,复杂度为 O(n)O(n)O(n),是解决此类问题的最佳选择。

通过优化,不仅提升了效率,还加深了对矩形问题的理解。这种思想在多个场景中具有应用价值,例如直方图最大面积问题和连续子区间问题等。