⭐算法入门⭐《栈 - 单调栈》困难02 —— LeetCode 85. 最大矩形

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了⭐算法入门⭐《栈 - 单调栈》困难02 —— LeetCode 85. 最大矩形相关的知识,希望对你有一定的参考价值。

🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《数据结构入门》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

一、题目

1、题目描述

  给定一个仅包含01、大小为rows x cols的二维二进制矩阵,找出只包含1的最大矩形,并返回其面积。
[ 0 1 0 0 0 0 1 1 1 0 1 1 1 0 1 1 ] \\left[ \\begin{matrix} 0 & 1 & 0 & 0\\\\ 0 & 0 & 1 & 1\\\\ 1 & 0 & 1 & 1\\\\1 & 0 & 1 &1\\end{matrix} \\right] 0011100001110111
  样例输入: [ ["0", "1", "0", "0"], ["0", "0", "1", "1"], ["1", "0", "1", "1"], ["1", "0", "1", "1"] ]
  样例输出: 6 6 6

2、基础框架

  • c++ 版本给出的基础框架代码如下:
int maximalRectangle(char** matrix, int matrixSize, int* matrixColSize){
}

3、原题链接

( 1 ) (1) (1) LeetCode 85. 最大矩形
( 2 ) (2) (2) 剑指 Offer II 040. 矩阵中最大的矩形

二、解题报告

1、思路分析

  • 考虑以矩阵的每行作为基准线,建立一个只有 1 直方图,如下图所示,表示的是以矩阵最后一行作为基准线,建立的直方图。
  • 于是问题就转化成求直方图中的最大矩形了。

1. 朴素做法

  • 首先,提供一个最暴力的办法,然后再来逐渐优化。
  • 枚举一个左方柱 l l l 和一个右方柱 r r r ,然后用 RMQ 区间最值求出区间的高度最小值 h m i n ( l , r ) hmin(l, r) hmin(l,r),那么答案就是: m a x ( ( r − l + 1 ) ∗ h m i n ( l , r ) ) max( (r-l+1)*hmin(l,r) ) max((rl+1)hmin(l,r)) ( 1 ≤ l ≤ r ≤ n ) (1 \\le l \\le r \\le n) (1lrn)
  • 这样做存在的问题就是时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的,而这个问题中 n n n 很大,我们需要想 O ( n ) O(n) O(n) 或者 O ( n l o g n ) O(nlogn) O(nlogn) 的方法。

2. 枚举左方柱

  • 延续之前的思路,我们还是枚举最左的那个方柱 l l l,那么它能够作为 “最左的那个方柱” 是有条件限制的,如图3所示:
    图3
  • 图中的方柱高度分别为 1,2,2,4,3 当枚举的 “最左的那个方柱” 为图中蓝色方柱(第四个方柱),那么以它为最左方柱的内接矩形的高度一定要大于它前一个方柱的高度,不然前一个方柱作为 “最左的那个方柱” ,得到的面积肯定会更大。
    图4
  • 如上图所示,以第四个方柱作为左端点,向右扩展时, ( a ) 、 ( b ) (a)、(b) (a)(b) 合法, ( c ) (c) (c) 非法(因为它的左方柱可以继续往左延伸)。

3. 固定右方柱

  • 然后,我们来看这么一种情况,从左往右扫描,扫描到第 i i i 个方柱,前面的方柱都是单调不减的,并且 h ( i − 1 ) > h ( i ) h(i-1) > h(i) h(i1)>h(i),如图5所示,蓝色方柱就是第 i i i 个方柱。
    图5
  • 这时候我们找到了几个满足 h ( l ) > h ( i ) h(l) > h(i) h(l)>h(i) 的最左方柱 l l l,并且计算了几块候选面积,如图6所示:

图6

  • 上图中的三种情况都是合法的,并且我们并不知道它们哪个面积大(红色代表每种方案的内接矩形的面积),试想一下图 ( a ) (a) (a) 中第六个方柱的高度无限高,那么它的面积明显就会比 图 ( b ) (b) (b)、图 ( c ) (c) (c)中的大,反之则不一定;
  • 对于所有的 h ( l ) > h ( i ) h(l) > h(i) h(l)>h(i) 的这些情况我们都需要计算一次,而且只需要计算一次,因为他们能够到达的最右方柱就是 r = i − 1 r = i-1 r=i1,和第 i − 1 i-1 i1 个方柱以后的方柱已经完全没有关系了,并且我们可以把计算后的图形和图7进行等价:
    图7
  • 于是,我们发现扫描完第 i i i 个方柱以后,前面的形状又变成了一个单调不减的,所以这个过程就变成了不断得维护一个单调不减的序列,严格一点,应该说是单调递增,因为高度相同的情况,下标越小越优。
  • 这一步我们称为削峰。

4. 维护单调递增序列

  • 通过一个 单调栈 st 来维护一个单调递增序列,栈里存的是 每个位置的下标,高度在栈内从栈底到栈顶单调递增。
  • 从左往右扫描每个方柱,对于一个栈 s,令 栈顶元素为 s t . t o p ( ) st.top() st.top() 考虑两种情况:
  • 1)空栈 或者 栈顶元素的高度 小于 当前元素的高度,即 h [ s t . t o p ( ) ] < h [ i ] h[st.top()] < h[i] h[st.top()]<h[i] 时,入栈;
  • 2)否则,循环判断栈顶元素,如果 栈顶元素的高度 大于等于 当前元素的高度,即 h [ s t . t o p ( ) ] ≥ h [ i ] h[st.top()] \\ge h[i] h[st.top()]h[i],出栈,并且更新最大面积 :
    a n s = m a x ( a n s , ( i − s t . t o p ( ) ) ∗ h [ s t . t o p ( ) ] ) ans = max(ans, (i - st.top()) * h[st.top()] ) ans=max(ans,(ist.top())h[st.top()])
  • 然后将最后出栈的那个元素继续入栈,并且将它的高度设置为 h [ i ] h[i] h[i] ,也就是上文提到的削峰。
  • 3)为了让所有元素都能出栈,我们需要在原高度最后面插入一个最小的数,即 -1。
  • 有关 的实现,可以参见以下文章:《画解数据结构》栈
  • 有关 单调栈 更多的内容,可以看以下这篇文章:夜深人静写算法(十一)- 单调栈

2、时间复杂度

  • 对于一个 n × m n \\times m n×m 的矩阵,求直方图时,由于每个元素最多入栈一次,出栈一次。时间复杂度为 O ( m ) O(m) O(m),枚举每条基准线的时间复杂度为 O ( n ) O(n) O(n)
  • 所以总的时间复杂度为 O ( n m ) O(nm) O(nm)

3、代码详解

/************************************* 栈的顺序表实现 *************************************/

#define DataType int
#define maxn 10010

struct Stack {
    DataType data[maxn];     // (1)
    int top;
};

void StackClear(struct Stack* stk) {
    stk->top = 0;
}
void StackPushStack(struct Stack *stk, DataType dt) {
    stk->data[ stk->top++ ] = dt;
}
void StackPopStack(struct Stack* stk) {
    --stk->top;
}

DataType StackGetTop(struct Stack* stk) {
    return stk->data[ stk->top - 1 ];
}
int StackGetSize(struct Stack* stk) {
    return stk->top;
}
bool StackIsEmpty(struct Stack* stk) {
    return !StackGetSize(stk);
}

/************************************* 栈的顺序表实现 *************************************/

struct Stack stk;

int max(int a, int b) {
    return a > b ? a : b;
}

int largestRectangleArea(int* heights, int heightsSize){
    int i, maxRet = 0;
    int topIndex;
    struct Stack *st = &stk;
    StackClear(st);                                // (2)

    for (i = 0; i <= heightsSize; ++i) {           // (3)
        if ( i < heightsSize && ( StackIsEmpty(st) || heights[ StackGetTop(st) ] <= heights[i]) ) {
            StackPushStack(st, i);                 // (4)
        }
        else {
            while (!StackIsEmpty(st) && (i == heightsSize || heights[ StackGetTop(st) ] >= heights[i]) ) {                                    // (5)
                topIndex = StackGetTop(st);
                StackPopStack(st);
                maxRet = max(maxRet, (i - topIndex) * heights[topIndex]);
            }
            StackPushStack(st, topIndex);          // (6)
            if(i < heightsSize)
                heights[topIndex] = heights[i];    // (7)
        }
    }
    return maxRet;
}
int row[210], tmprow[210];

int maximalRectangle(char** matrix, int matrixSize, int* matrixColSize){
    int i, j, col, ans, max = 0;
    memset(row, 0, sizeof(row));

    for(i = 0; i < matrixSize; ++i) {
        col = *matrixColSize;
        for(j = 0; j < col; ++j) {
            if ( matrix[i][j] == '1' ) {
                row[j] += 1;                         // (8)
            }else {
                row[j] = 0;
            }
        }
        for(j = 0; j < col; ++j) {
            tmprow[j] = row[j];                      // (9)
        }
        ans =

以上是关于⭐算法入门⭐《栈 - 单调栈》困难02 —— LeetCode 85. 最大矩形的主要内容,如果未能解决你的问题,请参考以下文章

单调栈算法 入门+博客推荐+模板

⭐算法入门⭐《栈 - 单调栈》简单01 —— LeetCode 155. 最小栈

单调栈

830. 单调栈

单调栈和单调队列

bzoj4750密码安全 单调栈