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

109 阅读4分钟

问题描述

小S最近在分析一个数组 h1,h2,...,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) 的最大值。


测试样例

样例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

问题解决

首先,我们注意到“R(k) 的值为这 k 个相邻元素中的最小值乘以 k”,即“区间查询问题”。区间查询问题立刻导向线段树ST表。由于不会对区间进行任何的改动,因此,使用查询更优的ST表,该方法查询时间复杂度为O(1)。

ST表

对于给定数组a,ST表为一不完全二维数组,其中st[i][j]表示从索引i开始,长度为2^j区间内的最值。

构建时,和查询一样,拆分成两个2^n长度的区间。不同区间相互重叠,以满足查询要求。

st[i][j]=min(A[i],A[i+1],,A[i+2j1])st[i][j]=min(A[i],A[i+1],…,A[i+2j−1])

当子区间长度为1时,

st[i][0]=A[i],0i<n\text{st}[i][0] = A[i], \quad \forall \, 0 \leq i < n

当子区间长度为2j2^j时,可以通过长度为2j12^{j-1}的两个子区间合并得到:

st[i][j]=min(st[i][j1],st[i+2j1][j1])\text{st}[i][j] = \min(\text{st}[i][j-1], \text{st}[i+2^{j-1}][j-1])

代码如下:

int RMQ(int L, int R, const vector<vector<int>>& st)
{
    int k = floor(log2(R - L + 1));
    return min(st[L][k], st[R - (1 << k) + 1][k]);
}

int solution(int n, vector<int> A) {
    vector<int>logtable(n + 1);
    logtable[1] = 0;
    for (int i = 2; i <= n; i++)
    {
        logtable[i] = logtable[i / 2] + 1;
    }
    vector<vector<int>>st(n, vector<int>(logtable[n] + 1));
    for (int i = 0; i < n; i++)
    {
        st[i][0] = A[i];
    }
    for (int j = 1; (1 << j) <= n; j++)
    {
        for (int i = 0; i + (1 << j) <= n; i++)
        {
            st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
    int result = 0;
    int temp = 0;

    for (int i = 0; i < A.size(); i++)
    {
        for (int j = i; j < A.size(); j++)
        {
            temp = (j - i + 1) * RMQ(i, j, st);
            result = max(temp, result);
        }
    }

    return result;
}

优化?

仅对于这道题的数据而言,这个算法已经可以完成任务。但我们还要更进一步。

不难发现,尽管查询时间为O(1),但是由于暴力查询,时间复杂度为O(n^2)。进一步的优化,考虑分治:每次递归分割成两个子区间,类似于二分,因此递归深度为 O(log⁡n)。总体的复杂度为O(nlogn)。

还不够快。尽管我们能够极快的查询区间最小值,我们还是要被“遍历所有可能的区间”所累。 解决办法是,单调栈

单调栈

单调栈是一种特殊的栈,它按照单调递增或单调递减的顺序组织栈中的元素。单调栈主要用于快速解决一些涉及区间、比较的问题,比如寻找数组中每个元素的左/右侧第一个比它大/小的元素,或者在柱状图中寻找最大矩形面积或滑动窗口问题。

每个元素只会被入栈和出栈一次,因此单调栈的整体时间复杂度是 O(n)

在这里,栈为空或当前柱子高度大于栈顶柱子高度时,当前柱子可能构成更大的矩形,因此直接入栈。 当前柱子高度小于栈顶柱子高度:说明以栈顶柱子为高度的矩形面积已确定。弹出栈顶柱子并计算矩形面积:矩形的宽度为:

当前索引 - 栈中下一个元素的索引 - 1

如果栈为空,则宽度为当前索引。据此,我们能在O(n)时间复杂度内完成本题。

int solution(int n, vector<int> A) {
    stack<int> s;
    int max_area = 0;
    int i = 0;

    while (i < n) {
        if (s.empty() || A[s.top()] <= A[i]) {
            s.push(i++);
        } else {
            int top = s.top();
            s.pop();
            int width = s.empty() ? i : i - s.top() - 1;
            int area = A[top] * width;
            max_area = max(max_area, area);
        }
    }

    while (!s.empty()) {
        int top = s.top();
        s.pop();
        int width = s.empty() ? i : i - s.top() - 1;
        int area = A[top] * width;
        max_area = max(max_area, area);
    }

    return max_area;
}

本题结束。