203、【栈与队列】leetcode ——剑指 Offer II 040. 矩阵中最大的矩形 / 85. 最大矩形:暴力+单调栈(C++/Pyhont版本)
题目描述
Problem: 剑指 Offer II 040. 矩阵中最大的矩形
文章目录
- 题目描述
- 解法一:暴力解法
- 思路
- 解题方法
- 复杂度
- Code
- 解法二:单调栈解法
- 思路
- 解题方法
- 复杂度
- Code
解法一:暴力解法
思路
首先, 按行获取到达某一元素位置时,之前与当前连续1的个数,这个连续1的个数就会作为所求矩阵的备选宽度。然后, 再迭代遍历,以列不变的基础上,从当前位置开始,自下而上改变行的位置,将原始位置与改变位置之差作为目标矩阵的备选长度,最后求得最大矩阵面积。
解题方法
计算矩阵宽度时,先以当前位置连续1的个数作为宽度,在后续更新时,因为要保证这个矩阵范围内都可以被囊括,就以迭代遍历中的最小宽度作为矩阵宽度。
计算矩阵面积时,用宽度乘以当前遍历到的行数之差,找出和已有结果对比的最大值,作为最大面积。
复杂度
- 时间复杂度:
O ( m 2 n ) O(m^2n) O(m2n)
其中 m 和 n 分别是矩阵的行数和列数。计算 left 矩阵需要 O ( m n ) O(mn) O(mn)的时间。随后对于矩阵的每个点,需要 O(m) 的时间枚举高度。故总的时间复杂度为 O ( m n ) + O ( m n ) ⋅ O ( m ) = O ( m 2 n ) O(mn)+O(mn)⋅O(m)=O(m^2n) O(mn)+O(mn)⋅O(m)=O(m2n)。
- 空间复杂度:
O ( m n ) O(mn) O(mn)
其中 m 和 n 分别是矩阵的行数和列数。我们分配了一个与给定矩阵等大的数组,用于存储每个元素的左边连续 1 的数量。
Code
C++
class Solution {
public:
int maximalRectangle(vector<string>& 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++) { // 记录每行中连续1的个数
for(int j = 0; j < n; j++) {
if(matrix[i][j] == '1') {
left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
}
}
}
int res = 0;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(matrix[i][j] == '0') { // 遇到0说明不是矩阵,则直接跳出
continue;
}
int width = left[i][j];
int area = width; // 此时的面积是以长度=1,宽度=left[i][j],得到的面积
for(int k = i - 1; k >= 0; k--) { // 因为之前记录的是每行连续的1(也就是长度),因此这里需要变化的是的整体的行长(宽度),从i-1开始
width = min(width, left[k][j]); // 找到当前范围内的最小宽度,遍历每种范围内的情况
area = max(area, (i - k + 1) * width); // 获取当列不变时,行从0~i之间的最大面积
}
res = max(res, area); // 找到整体的最大面积
}
}
return res;
}
};
Python
class Solution:
def maximalRectangle(self, matrix: List[str]) -> int:
m = len(matrix)
if m == 0:
return 0
n = len(matrix[0])
left = [[0 for _ in range(n)] for _ in range(m)]
res = 0
for i in range(m):
for j in range(n):
if matrix[i][j] == '1':
left[i][j] = left[i][j - 1] + 1 if j != 0 else 1
for i in range(m):
for j in range(n):
width, area = left[i][j], left[i][j]
for k in range(i - 1, -1, -1):
width = min(width, left[k][j])
area = max(area, (i - k + 1) * width)
res = max(res, area)
return res
参考文章:矩阵中最大的矩形
解法二:单调栈解法
思路
按行遍历,将每列连续的1变成直方图。连续1的个数,作为直方图的高度,每到一行的时候就遍历该行情况下直方图中的最大矩阵。以此方式,遍历完所有的行,得到全局的最大矩阵。其实就相当于是 188、【栈与队列】leetcode ——84. 柱状图中最大的矩形:暴力解法+单调栈(C++/Python版本) 的拓展。
解题方法
采用与柱状图中的最大矩阵相同的方式,只不过本体将其扩展成了二维的形式。按行遍历,每行在遍历各列的时候,就相当于是探寻一个柱状图中的最大矩形
对每个直方图的首尾位置都先加一个0作为哨兵,也就是在矩阵中的首尾两列加入0。然后,以在上一行的基础上计算当前行的直方图高度。最后,遍历每一行,在每一行中使用计算柱状图中最大的矩形的方式,计算每行中的最大矩形,最后再找到各行中柱状图中的最大矩形,从而得到答案。
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( m n ) O(mn) O(mn)
- 空间复杂度:
添加空间复杂度, 示例: O ( m n ) O(mn) O(mn)
Code
C++
class Solution {
public:
int maximalRectangle(vector<string>& matrix) {
int m = matrix.size();
if(m == 0) return 0;
int n = matrix[0].size();
vector<vector<int>> heights(m, vector<int>(n + 2, 0)); // 构建直方图矩阵,首尾各多加了个0作为哨兵
for(int i = 0; i < m; i++) {
for(int j = 1; j < n + 1; j++) {
if(matrix[i][j - 1] == '1') {
heights[i][j] = (i == 0 ? 0 : heights[i - 1][j]) + 1; // 按列统计连续的1的个数,个数情况作为直方图的高
}
}
}
int res = 0;
for(int i = 0; i < m; i++) { // 按行遍历直方图矩阵
// 获取该行的直方图信息,计算可构成的最大矩阵面积。(算法同柱状图中的最大矩阵一样)
int area = 0;
stack<int> st;
st.push(0);
for(int j = 1; j < heights[i].size(); j++) {
while(heights[i][st.top()] > heights[i][j]) {
int mid = st.top(); st.pop();
int w = j - st.top() - 1;
int h = heights[i][mid];
area = max(area, h * w);
}
st.push(j);
}
// 再计算不同行所构成的直方图中的最大矩阵面具
res = max(res, area);
}
return res;
}
};
Python
class Solution:
def maximalRectangle(self, matrix: List[str]) -> int:
m = len(matrix)
if m == 0:
return 0
n = len(matrix[0])
heights = [[0 for _ in range(n + 2)] for _ in range(m)]
for i in range(m):
for j in range(1, n + 1):
if matrix[i][j - 1] == '1':
heights[i][j] = heights[i - 1][j] + 1 if i != 0 else 1
res = 0
for i in range(m):
area = 0
st = [0]
for j in range(1, n + 2):
while heights[i][st[-1]] > heights[i][j]:
mid = st[-1]
st.pop()
w = j - st[-1] - 1
h = heights[i][mid]
area = max(area, w * h)
st.append(j)
res = max(res, area)
return res
参考视频:剑指offer040 矩阵中的最大矩形