[CP / LeetCode] 85. Maximal Rectangle - 最大矩形

解法一:二维最大子段和

分析

自己想出来的解法,但凡出题人把输入的规模改大一点就通过不了。

做这道题的时候我拼命在想它和前一道题(84. Largest Rectangle in Histogram - 柱状图中最大的矩形)的关系。如果每一列的 '1' 都是连续分布的,且 “贴牢地面”,那么本题就退化为了前一道题……可惜并不是这样。

由于本题的目标是找到 “最大” 的全 '1' 矩形,而且目前看下来没有什么能 “直接” 计算出最大值的办法,换言之,需要枚举所有可能的矩形,然后求面积,取最大值。

最暴力的方式是枚举矩形的两个端点,然后看看构成的矩形面积是否和矩形内 '1' 的数量相等,时间复杂度为 O(n6),进一步用二维前缀和优化到 O(n4),同时获得 O(n2) 的空间复杂度,nmax(row,col)

这样的时间复杂度显然是无法接受的。对于满足要求的矩形来说,它的每一列 / 每一行必定为全 '1',如果能利用此性质来构造目标矩形,便能缩小搜索范围,进而降低时间复杂度。

我联想起了以前做过的一道二维最大子段和题,它的时间复杂度是 O(n3),相似的方法在本题中能否发挥作用呢?事实证明是可以的。

首先需要计算出 col 个一维前缀和(或者 row 个一维前缀和),假设结果用二维数组 pre 存放,输入的二维数组为 matrix,则

preij=k=1imatrixkj

我们可以花费 O(1) 的时间查询同一列中,任意两个数 matrixmj,matrixnj (m<n) 及它们之间所有的数是否为全 '1'。

如果是,则有 prenjprem1,j=nm+1

假设某个矩形位于第 i 行和第 j 行之间(闭区间),遍历这两行之间的 col 个列,将全 '1' 列构成的矩形找出并计算面积,这一操作需要 O(col) 的时间。

而枚举所有的行-行组合需要 O(row2) 的时间。

所以总的时间复杂度为 O(row2×col),同样花费了 O(n2) 的空间复杂度,相比暴力做法,本方法将时间复杂度从 O(n4) 优化到了 O(n3),可以通过本题。

——但从学习的角度来说还不够,或者说,从数据中提取的互信息还不够多,所以请看解法二。

代码 - O(row2×col)

可以优化成 O(min(row,col)2×max(row,col)),虽然并没有什么用

class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
int row = matrix.size(), col = matrix[0].size();
std::vector<std::vector<int>> pre(row + 1);
for (auto& c : pre) {
c.resize(col + 1);
}
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
pre[i][j] = pre[i - 1][j] + matrix[i - 1][j - 1] - '0';
}
}
int ans = 0;
for (int i = 1; i <= row; i++) {
for (int j = i; j <= row; j++) {
int h = j - i + 1, t = 0, tm = 0;
for (int k = 1; k <= col; k++) {
if (pre[j][k] - pre[i - 1][k] == h) {
++t;
} else {
tm = std::max(tm, t);
t = 0;
}
}
tm = std::max(tm, t);
ans = std::max(ans, tm * h);
}
}
return ans;
}
};

运行结果

image

解法二:单调栈

分析

根据对称性,猜测理想的时间复杂度符合 O(row×col) 的形式,所以优化的关键在于如何去掉一层 row 循环。

如果去掉一层 row 循环,那么 “枚举某个列中的连续 '1' 字段” 就变成了 “枚举某个列中以某个元素为开头 / 结尾的连续 '1' 的个数”,这一操作依然可以用类似前缀和的方法实现,而原来的 col 循环可以使用和前一道题类似的单调栈。这样一来,时间复杂度就降低了。

……我承认这一做法不太容易讲明白。不过幸运的是,对于此方法,你能在题解区里找到好多清晰透彻的解释,因此请容许我在这里结尾吧。

代码 - O(row×col) - O(n2)

class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
int row = matrix.size(), col = matrix[0].size(), ans = 0;
std::vector<std::vector<int>> pre(row + 1);
for (auto& c : pre) {
c.resize(col + 1);
}
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
pre[i][j] = pre[i - 1][j] + matrix[i - 1][j - 1] - '0';
if (matrix[i - 1][j - 1] == '0') {
pre[i][j] = 0;
}
}
}
for (int i = 1; i <= row; i++) {
std::stack<int> s;
s.push(col + 1); // sentinel
for (int j = col; j >= 1; j--) {
while (!s.empty()) {
int sTop = s.top();
if (sTop == col + 1 ? 0 : pre[i][sTop] >= pre[i][j]) {
s.pop();
ans = std::max(ans, pre[i][sTop] * (s.top() - j - 1));
} else {
break;
}
}
s.push(j);
}
int n = s.size();
for (int k = 0; k < n - 1; k++) {
int sTop = s.top();
s.pop();
ans = std::max(ans, pre[i][sTop] * (s.top() - 1));
}
}
return ans;
}
};

运行结果

image

结尾的碎碎念

这是我解决的第 3 道困难题。虽然每道题都要思考数个小时之久,但是做出来的那一刻所带来的巨大成就感真是让人久久回味不能忘怀。我准备好接受更多挑战了!

posted @   ZXPrism  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示