Maximal Rectangle
Problem
Given a rows x cols binary matrix filled with 0's and 1's, find the largest rectangle containing only 1's and return its area.
Bonus if you can solve it in O(n^2) or less.
Examples
Example 1:
Input:
matrix = [
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
Output: 6
Explanation: The maximal rectangle is shown in the above picture.
Similar Problem
We have already seen the square matrix problem - [Maximal Square](maximal-square).
Solution
This problem can be converted to [Largest Rectangle in Histogram](largest-rectangle-in-histogram).
Method 1 - Brute Force
Here is the naive approach:
- Iterate Over All Possible Submatrices:
- Use nested loops to treat each cell
(i, j)as the top-left corner of a potential rectangle. - For each top-left corner, use nested loops to treat each cell
(p, q)as the bottom-right corner. - Check if the submatrix from
(i, j)to(p, q)contains only1s using theisAllOnesmethod. - If it does, calculate the area and update
maxAreaif the calculated area is larger.
- Use nested loops to treat each cell
- Helper Method (
isAllOnes): Check if all elements in the submatrix from(startX, startY)to(endX, endY)are1.
Code
Java
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix == null || matrix.length == 0) return 0;
int rows = matrix.length;
int cols = matrix[0].length;
int maxArea = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (matrix[i][j] == '0') continue;
for (int p = i; p < rows; p++) {
for (int q = j; q < cols; q++) {
if (matrix[p][q] == '0') continue;
if (isAllOnes(matrix, i, j, p, q)) {
int area = (p - i + 1) * (q - j + 1);
maxArea = Math.max(maxArea, area);
}
}
}
}
}
return maxArea;
}
private boolean isAllOnes(char[][] matrix, int startX, int startY, int endX, int endY) {
for (int x = startX; x <= endX; x++) {
for (int y = startY; y <= endY; y++) {
if (matrix[x][y] == '0') {
return false;
}
}
}
return true;
}
}
Python
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix or not matrix[0]:
return 0
rows = len(matrix)
cols = len(matrix[0])
max_area = 0
for i in range(rows):
for j in range(cols):
if matrix[i][j] == '0':
continue
for p in range(i, rows):
for q in range(j, cols):
if matrix[p][q] == '0':
continue
if self.is_all_ones(matrix, i, j, p, q):
area = (p - i + 1) * (q - j + 1)
max_area = max(max_area, area)
return max_area
def is_all_ones(self, matrix: List[List[str]], start_x: int, start_y: int, end_x: int, end_y: int) -> bool:
for x in range(start_x, end_x + 1):
for y in range(start_y, end_y + 1):
if matrix[x][y] == '0':
return False
return True
Complexity
- ⏰ Time complexity:
O((rows * cols)^3)as we're checking every possible sub-matrix. - 🧺 Space complexity:
O(1)as we only use a few additional variables.
Method 2 - Using Largest Rectangle in Histogram 🏆
We can use [Largest Rectangle in Histogram](largest-rectangle-in-histogram) here. So, our problem is:
- Row-wise convert matrix to histogram
- Then find the largest histogram.
So, this is how the approach will be:
- Dynamic Programming Table: We use a
heightsarray whereheights[j]represents the number of consecutive1s ending at columnjfor the current rowi. - Histogram Algorithm: For each row treated as the base, compute the largest rectangle area in the histogram represented by
heightsusing a stack-based approach. - Area Calculation: The maximum area found while iterating through rows is the answer.
Lets see how to convert the matrix in histogram.
Lets consider the matrix:
1 1 0 0 1 0
0 1 1 1 1 1
1 1 1 1 1 0
0 0 1 1 0 0
Convert first row to histogram, our heights for row[0]:
hist = 1 1 0 0 1 0
area = 2 (h = 1, l = 2)
First row is easy. For subsequent rows, whenver we see last row or base row having 0, we make our height 0, else we will sum up the pillar or column values.
Now, for row[2]:
hist = 0 2 1 1 2 1
area = 4 (h = 1, l = 4)
For row[3]:
1 3 2 2 3 0
area = 8 (h = 2, l = 4)
Finally row[4]:
0 0 3 3 0 0
area = 6 (h = 3, l = 2)
As, we can see row[3] histogram gave us our maximal area, that is the answer:
Code
Java
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0) {
return 0;
}
int rows = matrix.length;
int cols = matrix[0].length;
int[] heights = new int[cols];
int ans = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
heights[j] = matrix[i][j] == '1' ? heights[j] + 1 : 0;
}
ans = Math.max(ans, largestRectangleArea(heights));
}
return ans;
}
private int largestRectangleArea(int[] heights) {
int len = heights.length;
int maxArea = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i <= len; i++) {
int h = (i == len) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
}
Python
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix:
return 0
rows, cols = len(matrix), len(matrix[0])
heights: List[int] = [0] * cols
ans: int = 0
for i in range(rows):
for j in range(cols):
heights[j] = heights[j] + 1 if matrix[i][j] == '1' else 0
ans = max(ans, self.largestRectangleArea(heights))
return ans
def largestRectangleArea(self, heights: List[int]) -> int:
stack: List[int] = []
max_area: int = 0
len_heights: int = len(heights)
for i in range(len_heights + 1):
h = heights[i] if i < len_heights else 0
while stack and h < heights[stack[-1]]:
height: int = heights[stack.pop()]
width: int = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_area
Complexity
- ⏰ Time complexity:
O(rows * cols)because we iterate through each element of the matrix once and compute the area for each row-constructed histogram inO(cols). - 🧺 Space complexity:
O(cols)for theheightsarray and the stack used in the histogram calculation.
Method 3 - Using DP
The dynamic programming (DP) solution processes the matrix row by row, starting from the first row. The maximal rectangle area at row i and column j can be calculated as [right(i,j) - left(i,j)] * height(i,j). The variables left, right, and height can be updated using information from both the current row and the previous row, making this a DP solution. The transition equations are:
left(i, j) = max(left(i-1, j), cur_left)wherecur_leftis derived from the current row.right(i, j) = min(right(i-1, j), cur_right)wherecur_rightis derived from the current row.height(i, j) = height(i-1, j) + 1ifmatrix[i][j] == '1'.height(i, j) = 0ifmatrix[i][j] == '0'.
Intuition and Approach
We iterate over each row, updating the height, left, and right arrays:
height[i]records the number of consecutive1s in columni.left[i]records the leftmost indexjwhere, for all indiceskfromjtoi, the height is at leastheight[i].right[i]records the rightmost indexjwhere, for all indiceskfromitoj, the height is at leastheight[i].
1. Updating height
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
height[j]++;
} else {
height[j] = 0;
}
}
2. Updating left
(left boundary, scanning left to right):
int leftBoundary = 0;
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
left[j] = Math.max(left[j], leftBoundary);
} else {
left[j] = 0;
leftBoundary = j + 1;
}
}
Updating right
(right boundary, scanning right to left):
int rightBoundary = n;
for (int j = n - 1; j >= 0; j--) {
if (matrix[i][j] == '1') {
right[j] = Math.min(right[j], rightBoundary);
} else {
right[j] = n;
rightBoundary = j - 1;
}
}
Code
Java
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int m = matrix.length;
int n = matrix[0].length;
int maxArea = 0;
int[] left = new int[n];
int[] right = new int[n];
int[] height = new int[n];
Arrays.fill(right, n);
for (int i = 0; i < m; i++) {
int currentLeft = 0, currentRight = n;
// Update heights
for (int j = 0; j < n; j++) {
height[j] = (matrix[i][j] == '1') ? height[j] + 1 : 0;
}
// Update left boundary
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
left[j] = Math.max(left[j], currentLeft);
} else {
left[j] = 0;
currentLeft = j + 1;
}
}
// Update right boundary
for (int j = n - 1; j >= 0; j--) {
if (matrix[i][j] == '1') {
right[j] = Math.min(right[j], currentRight);
} else {
right[j] = n;
currentRight = j;
}
}
// Update max area
for (int j = 0; j < n; j++) {
maxArea = Math.max(maxArea, (right[j] - left[j]) * height[j]);
}
}
return maxArea;
}
}
Python
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
max_area = 0
left = [0] * n
right = [n] * n
height = [0] * n
for i in range(m):
current_left, current_right = 0, n
# Update heights
for j in range(n):
if matrix[i][j] == '1':
height[j] += 1
else:
height[j] = 0
# Update left boundary
for j in range(n):
if matrix[i][j] == '1':
left[j] = max(left[j], current_left)
else:
left[j] = 0
current_left = j + 1
# Update right boundary
for j in range(n - 1, -1, -1):
if matrix[i][j] == '1':
right[j] = min(right[j], current_right)
else:
right[j] = n
current_right = j
# Update max area
for j in range(n):
max_area = max(max_area, (right[j] - left[j]) * height[j])
return max_area