【leetcode】85 最大矩形(动态规划,栈)

题目链接:https://leetcode-cn.com/problems/maximal-rectangle/

题目描述

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

示例:

输入:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]
输出: 6

【leetcode】85 最大矩形(动态规划,栈)_第1张图片

思路

1 使用柱状图的优化暴力方法

【leetcode】85 最大矩形(动态规划,栈)_第2张图片
设置二维数组dpdp[i][j]表示以该坐标结束的矩形的最大可能宽度。

dp[i][j] = dp[i][j-1] + 1 if matrix[i][j] == '1'

一旦我们知道每个点对应的最大宽度,就可以以线性时间内计算出以该点为右下角的最大矩形。我们从该位置第i行往上遍历该列,可以得到从初始点(i,j)到当前点矩形的最大宽度,就是我们遇到的每个最大宽度d[k][j]的最小值。
我们定义

currentWidth = max(currentWidth, dp[i][k])
currentArea = currentWidth * (i - k + 1)
maxArea = max(maxArea, currentArea)

对每个点重复这一过程,就可以得到全局最大。
注意,我们预计算最大宽度的方法事实上将输入转化成了一系列的柱状图,每一栏是一个新的柱状图。我们在针对每个柱状图计算最大面积。

【leetcode】85 最大矩形(动态规划,栈)_第3张图片
于是,上述方法本质上是 84 - 柱状图中最大的矩形 题中优化暴力算法的复用。

复杂度分析
时间复杂度: O ( m 2 n ) O(m^2n) Om2n
空间复杂度: O ( m n ) O(mn) Omn
m,n为输入矩阵的行列数

2 动态规划-每个点的最大高度

想象一个算法,对于每个点我们会通过以下步骤计算一个矩形:

不断向上方遍历,直到遇到“0”,以此找到矩形的最大高度。

向左右两边扩展,直到无法容纳矩形最大高度。

例如,找到黄色点对应的矩形:
【leetcode】85 最大矩形(动态规划,栈)_第4张图片

我们知道,最大矩形必为用这种方式构建的矩形之一。

给定一个最大矩形,其高为 h, 左边界 l,右边界 r,在矩形的底边,区间 [l, r]内必然存在一点,其上连续1的个数(高度)<=h。若该点存在,则由于边界内的高度必能容纳h,以上述方法定义的矩形会向上延伸到高度h,再左右扩展到边界 [l, r] ,于是该矩形就是最大矩形。

若不存在这样的点,则由于[l, r]内所有的高度均大于h,可以通过延伸高度来生成更大的矩形,因此该矩形不可能最大。

综上,对于每个点,只需要计算hl,和 r - 矩形的高,左边界和右边界。

使用动态规划,我们可以在线性时间内用上一行每个点的 hl,和 r 计算出下一行每个点的的hl,和r

算法

给定一行 matrix[i],我们通过定义三个数组heightleft,和 right来记录每个点的hl,和 rheight[j] 对应matrix[i][j]的高,以此类推。

问题转化为如何更新每个数组。

Height:

这个比较容易。 h 的定义是从该点出发连续的1的个数。

row[j] = row[j - 1] + 1 if row[j] == '1'

只需要一点改动即可:

new_height[j] = old_height[j] + 1 if row[j] == '1' else 0

Left:

考虑哪些因素会导致矩形左边界的改变。由于当前行之上的全部0已经考虑在当前版本的left中,唯一能影响left就是在当前行遇到0

因此我们可以定义:

new_left[j] = max(old_left[j], cur_left)
cur_left是我们遇到的最右边的0的序号加1。当我们将矩形向左 “扩展” ,我们知道,不能超过该点,否则会遇到0。

Right:

我们可以沿用 left 的思路,定义:

new_right[j] = min(old_right[j], cur_right)

cur_right 是我们遇到的最左边的0的序号。简便起见,我们不把 cur_right 减去1 (就像我们给cur_left加上1那样) ,这样我们就可以用height[j] * (right[j] - left[j]) 而非height[j] * (right[j] + 1 - left[j])来计算矩形面积。

这意味着, 严格地说 ,矩形的底边由半开半闭区间[l, r) 决定,而非闭区间 [l, r],且 right比右边界大1。尽管不这样做算法也可以正确运行,但这样会让计算看起来更简洁。

注意,为了正确的记录 cur_right,我们需要从右向左迭代。因此,更新right时需要从右向左。

一旦leftright,和 height数组能够正确更新,我们就只需要计算每个矩形的面积。

由于我们知道矩形 j的边界和高,可以简单地用height[j] * (right[j] - left[j])来计算面积,若j的面积 大于max_area,则更新之。

复杂度分析
时间复杂度: O ( m n ) O(mn) Omn
空间复杂度: O ( n ) O(n) On
m,n为输入矩阵的行列数

/*
 * 动态规划
 * 时间复杂度O(mn) 空间复杂度O(n)
 * m,n为数组行列数
 */
class SolutionII {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.empty() || matrix[0].empty()) return 0;
        int rows = matrix.size(), cols = matrix[0].size();
        vector<int> left(cols, 0);      // 最左边的1位置 左边界
        vector<int> right(cols, cols);  // 右边界
        vector<int> height(cols, 0);
        int maxArea = 0;
        for (int i = 0; i < rows; ++i) {
            int curLeft = 0;            // 遇到的最右边的0的序号加1
            int curRight = cols;        // 遇到的最左边的0的序号
            for (int j = 0; j < cols; ++j) {
                if(matrix[i][j] == '1')
                    height[j] += 1;
                else
                    height[j] = 0;
            }
            // 更新left
            for (int j = 0; j < cols; ++j) {
                if (matrix[i][j] == '1')
                    left[j] = max(left[j],curLeft);
                else{
                    left[j] = 0;
                    curLeft = j+ 1;
                }
            }
            // 更新right
            for (int j = cols-1; j >=0 ; --j) {
                if (matrix[i][j] == '1')
                    right[j] = min(right[j],curRight);
                else{
                    right[j] = cols;
                    curRight = j;
                }
            }
            // 更新最大面积
            for (int j = 0; j < cols; ++j) {
                maxArea = max(maxArea, height[j] * (right[j] - left[j]));
            }
        }
        return maxArea;
    }
};

【leetcode】85 最大矩形(动态规划,栈)_第5张图片

你可能感兴趣的:(LeetCode)