LeetCode - Container With Most Water、Largest Rectangle in Histogram - 直方图中面积问题

本文会总结以下两道题目:

11 - Container With Most Water - 直方图中盛水问题 (Medium)

84 - Largest Rectangle in Histogram - 直方图中最大矩形面积问题 (Hard)

还有两道题目也是直方图中面积问题,在另一篇中总结,(还没写- -)

42 - Trapping Rain Water - 直方图中接雨水问题 (Hard)

407 - Trapping Rain Water II - 42题的二维变三维 (Hard)


11. Container With Most Water

Given n non-negative integers a1a2, ..., an , where each represents a point at coordinate (iai). n vertical lines are drawn such that the two endpoints of line i is at (iai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant the container and n is at least 2.

The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7].

In this case, the max area of water (blue section) the container can contain is 49.

解:

    这道题的意思就是用直方图中任意两个柱子当边界,中间部分盛水,求最大的能盛水的多少,也就是最大的形成的矩形的面积。很明显,由于短板效应,能盛水多少取决于两个板子中间短的那个,而且中间部分是完全不用考虑的,两个任意板子 h[i] 和 h[j] (0 <= i < j <= n)形成的面积就是 min(h[i], h[j]) * (j - i)

    出于以上分析,这道题就很适合用夹逼的方式计算,也就是两个哨兵分别从最左和最右向内收,直到两个哨兵相遇位置结束。我们需要设计的就是两个哨兵想内遍历时候的判断方式,也就是到底谁动。代码结构如下:

int maxArea(vector<int>& height)
{
    int res;
    int l = 0, r = height.size() - 1;
    while(l < r)
    {
        // 计算res
    }
    return res;
}

    由于上面提到的短板效应,两个板子中间短的决定矩形面积,长的板子是没有实质性作用的。如果我们将长的板子的哨兵向内移动,短的不动的话,无论如何矩形面积都不可能变大的,就算板子变长了也不会影响盛水高度,反而由于板子之间距离减少了一而使得面积变小。所以在哨兵移动的时候,应该是短的向内移动,长的板子不动,只有这样才有可能让盛水高度变高,这样才有可能在板子间距离变少的情况下,盛水总量变大。两个板子一样高的情况下,两个哨兵哪个向内移动都可以。分析后很容易写出代码:

int maxArea(vector<int>& height)
{
    int l = 0, r = height.size() - 1, max_water = -1;
    while(l < r)
    {
        max_water = max(max_water, (r - l) * min(height[l], height[r]));
        if(height[l] < height[r])   ++l;
        else                        --r;
    }
    return max_water;
}

84. Largest Rectangle in Histogram

Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.


Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].


The largest rectangle is shown in the shaded area, which has area = 10 unit.

Example:

Input: [2,1,5,6,2,3] Output: 10

解:

    这道题和上边的盛水一样是求最大矩形面积,不过约束条件从两端的板子高度变成了板子内部,也就是在这个直方图所有板子组成的图形中画矩形,面积最大的是多少。

    由于约束条件是最短的板子,所以用夹逼的方式是不行的(中间的板子可能比两端的板子更低)。应该是从每个板子出发,看看以这个板子为高度最大的矩形面积是多少,比如上图中第一个高度为2的板子,高为2,宽就只有1,因为后边的1比它低。也就是说,对于每个板子,从它所在位置分别向左向右走,走到比这个它低的板子停止,这些和它连着的高度 >= 它的板子组合起来就是当前这个板子为高度组成的最大矩形。简单地说就是,对于每个板子,矩形的宽就是板子的高,我们要计算的就是矩形的长。AC代码如下:

int largestRectangleArea(vector<int>& heights)
{
    int res = 0;        // 如果heights是空的,直接返回0
    int len = heights.size();
    for(int i = 0; i < len; i++)
    {
        if(i > 0 && heights[i] == heights[i - 1])
            continue;
        int l = i - 1, r = i + 1;
        while(l >= 0 && heights[l] >= heights[i])
            --l;
        while(r <= len - 1 && heights[r] >= heights[i])
            ++r;
        res = max(res, (r - l - 1) * heights[i]);
    }
    return res;
}

     算法思路的核心就是:限制一个板子能组成的矩形面积的是,它左边第一个比它低的板子,和它右边第一个比它低的板子。上边的代码虽然AC,但是效率较低,主要是因为每个板子相当于遍历整个直方图,时间复杂度为 O(n^2) 。对于每个板子,我们其实不需要遍历它左右的所有板子,如果我们存起来它前边的板子,我们就知道它左边能到哪里了。

    我们可以利用一个stack,比如叫 bar_stack,从左往右遍历直方图的每个板子 bar[i],如果 bar_stack.top() < bar[i] ,那就说明这个板子比 stack里的都高,push进stack,如果 bar_stack.top() = bar[i],那就说明有连续的相同高度的板子,但是不能和上边的代码一样直接跳过,不过进行 if 和 else 那种处理都行。重点是,如果 bar_stack.top() > bar[i],那就是说,我们找到了 bar_stack.top() 这个板子右边第一个比它低的板子!!而stack中它前一个板子一定它左边第一个比它低的板子!!我们就很容易计算这个板子组成的矩形了,也就是(右边板子的索引 - 左边板子的索引)* bar_stack.top() 这个板子的高度

    理解了上面的分析(更加详细的英文分析在 https://www.geeksforgeeks.org/largest-rectangle-under-histogram/),就能明白 stack 里push的不是板子高度,而是板子索引,这样就很容易写出代码。有一个小细节就是,因为 bar[i] 可能会比stack中不止一个bar高度要低,所以需要pop很多次,而且对于小的 bar[i] ,i 还是要放进去的,所以用 while 会比 for 更好写。(英文讲解文档中相同高度的板子也被放入stack中,也就是push之后,再pop,不过 hist[s.top()] <= hist[i] 这个判断改为 < 也是对的,也就是pop出去,再push)。比如 3, 9, 9, 9 这个例子,每个9用这个算法算出来的是不一样的结果,不过最终算出来最大的是对的。感觉这道题这么做还是挺复杂的,还不如用我第一种方法。。。。。但是这种方法快,省去了从每块板子向左向右的遍历。

int largestRectangleArea(vector<int>& heights)
{
    int res = 0, i = 0;
    stack<int> hs;
    while (i < heights.size())
    {
        if (hs.empty() || heights[i] >= heights[hs.top()]) // 比它高,或相同就push进去
            hs.push(i++);
        else if (heights[i] < heights[hs.top()])          // 重点部分
        {
            int t = hs.top();
            hs.pop();
            res = max(res, heights[t] * (hs.empty() ? i : i - hs.top() - 1));	// i 表示的是 i - 0
        }
    }
    // 现在 i 是 heights.size()
    // 所有height遍历完成,还要将hs遍历一遍,如果12345,那现在stack里就是12345,res还没有计算过
    while (!hs.empty())
    {
        int t = hs.top();
        hs.pop();
        res = max(res, heights[t] * (hs.empty() ? i : i - hs.top() - 1));	// i 表示的是 i - 0
    }
    return res;
}

猜你喜欢

转载自blog.csdn.net/Bob__yuan/article/details/82693548