给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

1. 暴力求解

该问选择合适的暴力方式也有一定的难度,既要遍历所有可能的矩形,又要尽可能减少重复运算
可以考察以每个点为右下角的最大矩阵,即在二重循环遍历中,计算每个点所有高度的面积
这样我们就能实现通过递推的方式事先算出需要用到的值,减少重复运算
left[i][j] 为矩阵第 i 行第 j 列元素的左边连续 1 的数量,这个信息可以通过递推事先计算出来
其本质是将这部分区域矩形转换成了柱状图,再求柱状图的最大矩形面积

//要使用暴力求解的话,遍历每一个点,要能对所有矩阵进行考察
        //可以考察以该点为右下角的最大矩阵,即在二重循环遍历中,计算每个点所有高度的面积
        //时间复杂度为O(m2n)
        //left[i][j] 为矩阵第 i 行第 j 列元素的左边连续 1 的数量,也就是矩形的宽度
class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m = matrix.size();//记录行数
        if (m == 0) return 0;
        int n = matrix[0].size();//记录列数
        vector<vector<int>> left(m, vector<int>(n, 0));//动态规划辅助计算

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') 
                //计算第 i 行第 j 列元素的左边连续 1 的数量,方便矩形面积计算
                //本质上就是将其转换成以该点为右下边界所在区域的柱状图模型
                    left[i][j] = (j == 0 ? 0: left[i][j - 1]) + 1;
            }
        }

        int ret = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //跳过为0的矩阵减少运算
                if (matrix[i][j] == '0') continue;
                int width = left[i][j];//记录当前宽度
                int area = width;
                for (int k = i - 1; k >= 0; k--) {//遍历所有高度
                    width = min(width, left[k][j]);//更新宽度
                    area = max(area, (i - k + 1) * width);//计算并更新该点最大面积
                }
                ret = max(ret, area);//更新所有点对应最大面积
            }
        }
        return ret;
    }
};

2. 单调栈

既然方法一的实质是转换成柱状图再对每个柱状图遍历,那可以结合之前求柱状图中最大矩形的优化方式来进一步优化

单调栈优化

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m = matrix.size();//记录行数
        if (m == 0) return 0;
        int n = matrix[0].size();//记录列数
        vector<vector<int>> up(m, vector<int>(n, 0));//动态规划辅助计算
        for (int i = 0; i < m; i++) 
            for (int j = 0; j < n; j++) 
                if (matrix[i][j] == '1') 
                    up[i][j] = (i == 0 ? 0: up[i-1][j]) + 1;//转换成柱状图
        int res = INT_MIN;
        for(int i = 0; i < m; i++)
            res = max(res,largestRectangleArea(up[i]));//从上往下计算每一层作为底层的柱状图
        return res;
    }

    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n), right(n);//记录每个柱子左右,满足条件的最远下标
                                    //条件指得是第一个小于柱子高度的位置
        
        stack<int> mono_stack;//单调栈
        for (int i = 0; i < n; ++i) {//从左往右遍历找每个元素左侧满足条件最远位置

            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();//如果左边这个元素大于当下节点,那他是包容右边的
                //提供了当下节点构成左边矩形区域一部分
                //所以可以不用看了,直接去掉,因为它被当下节点截断了,不会再影响后面元素
                //同时要露出我们的左边界
            }
            //如果满足增序关系,那左边界就是他前一个元素
            left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
            //当下节点的左边界,即是左边第一个小于它的元素,因为大的元素全部去掉了
            mono_stack.push(i);//当下节点入栈,给后面元素继续判断
        }

        mono_stack = stack<int>();//单调栈
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.empty() ? n : mono_stack.top());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
};