[leetcode] Maximal Rectangle

一、写在前面的废话(写给自己以后看)

(1)已经有两个多月的时间没写了,这意味着这两个月没什么值得记录的东西,也意味着没怎么写代码,不好的征兆,要加油了!
(2)做这题的时候,各种乱七八糟的思路满天飞,结果没有一个是对的,折腾一晚上,失败告终,然后向大神请教,终得一解法(发现自己的思路错在了最后一步上,开心又难过)

二、题目

Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.

给一个 2 维数组,每个点不是 0 就是 1 ,找到最大的只包含 1 的矩形,并返回最大的面积。

例子:
For example, given the following matrix:

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

Return 6.

三、解法

这题,网上有 n2 解法,不过,还没琢磨明白,先记一下当前懂的方法(能跑通LeetCode所有测试用例),后面再继续填坑。

暴力求解就不考虑了,写出来也肯定通不过测试数据量比较大时的测试用例,鉴于暴力求解的复杂度是 n4 (假设输入为n*n的矩阵),想一想能不能有 n3 的解法。

解法1: n3 解法
大体思路:

  • 以每一行为子矩形的下底边,遍历每一个可能的底边,然后求出以该边为底边的矩形高最大是多大,最终,矩形面积=底边长度*高度,取最大即可。
  • 那么,这个高度该怎么求呢?可以维护一个二维数组存储以当前点为底,往上搜索有多少个连续的 1 ,即为当前点的高度。
  • 其实这个想法也就是,固定底边,找最大高。

代码思路:

  • new一个二维数组 scanning_column 用来存储每个结点的高度
  • 维护 scanning_column 数组

    • 从上到下,从左到右扫描测试用例数组的每一个结点
      • 如果该节点是‘0’,则 scanning_column 相应位置 = 0
      • 如果该节点非‘0’,则 scanning_column 相应位置 = 1 + scanning_column 数组中上一个结点值
    • 在维护该节点高度值时,如果非‘0’,在更新完高度值后,检查以该节点为子矩形底边右顶点,以向左搜索始终未遇到0的所有点为底边左顶点的底边的最小高度(遇到0即可停止搜索)(着实有点绕,好在代码很简单),此最小高度即为该底边对应的最大子矩形的高度,然后即可求出此子矩形的面积,更新当前最大子矩形面积值。
  • 扫描完毕即可得到结果

代码实现:
Java版

class Solution {
    public int maximalRectangle(char[][] matrix) {
        if(matrix.length == 0 || matrix[0].length == 0) //matrix为空,直接返回即可
            return 0;
        int row = matrix.length;  //行
        int column = matrix[0].length;  //列
        int[][] scanning_column = new int[row][column];//维护每一个结点高度的二维数组
        int max = 0; //最终返回值
        for(int i = 0; i < row; i++){
            for(int j = 0; j < column; j++){
                if(matrix[i][j] == '0'){
                    scanning_column[i][j] = 0;
                }else{
                    if(i>=1){//非第一行,第一行没有上一行
                        scanning_column[i][j] = scanning_column[i-1][j]+1;
                    }else{//第一行,是1还是0直接抄上即可
                        scanning_column[i][j] = 1;
                    }
                    //计算以当前点为底边右下角点,同行左边所有连续非0点为左下角点时可得到的最大矩阵
                    int min = scanning_column[i][j]; //记录右下角点到左下角点间最小高度
                    for(int k = j; k >= 0; k--){
                        if(scanning_column[i][k] == 0 )
                            break;//遇到0直接跳出此次循环即可
                        if(scanning_column[i][k] < min){
                            min = scanning_column[i][k];
                        }
                        max = (j-k+1)*min > max ? (j-k+1)*min : max;//更新max值
                    }
                }
            }
        }
        return max;
    }
}

Python版(最近在学,练练手)写法跟上面是一样的,只是使用Python的语法而已

class Solution(object):
    def maximalRectangle(self, matrix):
        """ :type matrix: List[List[str]] :rtype: int """
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return 0;
        row = len(matrix);  #行
        column = len(matrix[0]);  #列
        scanning_column = [[0 for i in range(column)] for j in range(row)];
        max_area = 0;
        for i in range(row):
            for j in range(column):
                if matrix[i][j] == '0':
                    scanning_column[i][j] = 0;
                else:
                    if i>=1:
                        scanning_column[i][j] = scanning_column[i-1][j]+1;
                    else:
                        scanning_column[i][j] = 1;
                    #计算以当前点为右下角点,同行左边所有点为左下角点时可得到的最大矩阵
                    min_height = scanning_column[i][j]; #记录右下角点到左下角点间最小高度
                    for k in range(j, -1, -1):
                        if scanning_column[i][k] == 0break;
                        if scanning_column[i][k] < min_height:
                            min_height = scanning_column[i][k];
                        max_area = (j-k+1)*min_height if (j-k+1)*min_height > max_area else max_area;
        return max_area;

解法2: n4 解法
为什么要记录一下这个解法呢?
(1)第一版的实现卡死在了最后一个测试用例上,超时了。但是,在努力改成 n3 之后居然意外通过了,有点小兴奋。
(2)一开始分析的时候觉得也是 n3 可解的,但是实际写起代码来才发现是 n4 的,改来改去想变成 n3 的,但最终发现,在我能力范围之内是不可能的,但既然写了,就还是记录一下吧。

大体思路:

  • 每相邻的 j (1<=j<=n)行对应列相加,找出当前加和为 j 的连续个数最大值(即最多有几个 j 相邻),当前最大 = j * 连续个数。
  • 其实这个想法也就是,固定高,找最长底边

代码思路:

  • 将给的 char 型二维数组转化成 int 型二维数组
  • 维护一个 cur_left 值,记录连续 j 的最左边那个 j 的前一个值的下标
  • 从第 0 行开始,每次将相邻的 j 行相加,从左到右依次扫描,
    • 若加和为 j ,则说明当前列的 j 行均为 1,更新最大值
      • 最大值为=max{当前最大, j * 连续个数}
    • 若加和不为 j,则说明当前列有 0,高度不为 j ,更新 cur_left 为当前元素下标

Java实现:

class Solution {
    public int maximalRectangle(char[][] matrix) {
        if(matrix.length == 0 || matrix[0].length == 0)
            return 0;
        int row = matrix.length;  //行
        int column = matrix[0].length;  //列
        int[][] mat = new int[row][column];
        for(int a = 0; a < row; a++){
            for(int b = 0; b < column; b++){
                if(matrix[a][b] == '1')
                    mat[a][b] = 1;
                else
                    mat[a][b] = 0;
            }
        }
        int max = 0;
        int cur = 0;
        int cur_left = -1;//记录与cur相等的最左边值的前一个值的下标
        for(int i = 0; i < row; i++){
            for(int j = 1; j <= row-i; j++){ //mat每j行相加,计算出相邻值均为j的个数,j*个数=子矩阵面积
                cur_left = -1;
                for(int m = 0; m < column; m++){//每j行相加,计算出目前为止最大子矩阵
                    for(int k = i; k < i+j && k < row; k++){
                        cur = k == i ? mat[k][m] : (cur + mat[k][m]);
                    }
                    if(cur == j){
                        max = (cur * (m - cur_left)) > max ? (cur * (m - cur_left)) : max;
                    }else{
                        cur_left = m;
                    }
                }
            }
        }
        return max;
    }
}
点赞