1727. Largest Submatrix With Rearrangements - Explanation
Description
You are given a binary matrix matrix of size m x n, and you are allowed to rearrange the columns of the matrix in any order.
Return the area of the largest submatrix within matrix where every element of the submatrix is 1 after reordering the columns optimally.
Example 1:
Input: matrix = [
[0,0,1],
[1,1,1],
[1,0,1]
]
Output: 4Explanation: One optimal rearrangement moves the last column first, then the first column, then the second column:
[
[1,0,0],
[1,1,1],
[1,1,0]
]The bottom-left 2 x 2 submatrix contains only 1s, so the largest area is 4.
Example 2:
Input: matrix = [
[1,1,0],
[1,0,1]
]
Output: 2Explanation: Notice that you must rearrange entire columns, and there is no way to make a submatrix of 1s larger than an area of 2.
Constraints:
1 <= (matrix.length * matrix[i].length) <= 100,000matrix[i][j]is either0or1.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- 2D Matrix Traversal - Understanding how to iterate through rows and columns of a matrix
- Greedy Algorithms - Making locally optimal choices to find a global optimum
- Sorting - Sorting arrays and understanding how sorted order enables efficient computations
- Histogram/Height Arrays - Building cumulative heights column-wise (similar to "Largest Rectangle in Histogram")
1. Brute Force
Intuition
We can rearrange columns in any order, so the key is to find which columns can form a rectangle of all 1s. For each starting row, we track which columns have continuous 1s from that row downward. As we extend the rectangle row by row, columns with a 0 are eliminated. The area at each step is the number of surviving columns multiplied by the current height.
Algorithm
- For each starting row, initialize a set containing all column indices.
- Iterate through each subsequent row:
- Remove columns that have a
0in the current row. - Calculate the area as
(remaining columns) * (current height). - Update the maximum area found.
- Remove columns that have a
- Return the maximum area.
class Solution:
def largestSubmatrix(self, matrix: List[List[int]]) -> int:
ROWS, COLS = len(matrix), len(matrix[0])
res = 0
for start_row in range(ROWS):
ones = deque(list(range(COLS)))
for r in range(start_row, ROWS):
if not ones:
break
for _ in range(len(ones)):
c = ones.popleft()
if matrix[r][c] == 1:
ones.append(c)
res = max(res, len(ones) * (r - start_row + 1))
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
2. Greedy + Sorting
Intuition
Think of each cell as the height of a bar extending upward through consecutive 1s. For each row, compute these heights based on the previous row. Since we can rearrange columns, sort the heights in descending order. Then greedily compute the largest rectangle: the first column can use its full height, the first two columns are limited by the second height, and so on.
Algorithm
- Maintain a
heightsarray tracking consecutive1s above each cell. - For each row:
- Update heights: if the cell is
1, add the previous height; otherwise reset to0. - Sort heights in descending order.
- For each position
i, compute area as(i + 1) * heights[i]and track the maximum.
- Update heights: if the cell is
- Return the maximum area found.
class Solution:
def largestSubmatrix(self, matrix: List[List[int]]) -> int:
ROWS, COLS = len(matrix), len(matrix[0])
res = 0
prev_heights = [0] * COLS
for r in range(ROWS):
heights = matrix[r][:]
for c in range(COLS):
if heights[c] > 0:
heights[c] += prev_heights[c]
sorted_heights = sorted(heights, reverse=True)
for i in range(COLS):
res = max(res, (i + 1) * sorted_heights[i])
prev_heights = heights
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
3. Greedy + Sorting (Overwriting the Input)
Intuition
This is the same approach as above but optimizes space by modifying the input matrix directly. Each cell stores the cumulative height of consecutive 1s ending at that cell. This eliminates the need for a separate heights array.
Algorithm
- For each row starting from the second:
- If a cell is
1, add the value from the cell directly above it.
- If a cell is
- For each row:
- Sort the row in descending order.
- Compute the maximum area using the greedy formula.
- Return the maximum area.
class Solution:
def largestSubmatrix(self, matrix: List[List[int]]) -> int:
ROWS, COLS = len(matrix), len(matrix[0])
res = 0
for r in range(1, ROWS):
for c in range(COLS):
if matrix[r][c]:
matrix[r][c] += matrix[r - 1][c]
for r in range(ROWS):
matrix[r].sort(reverse=True)
for i in range(COLS):
res = max(res, (i + 1) * matrix[r][i])
return resTime & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algoirhtm.
Where is the number of rows and is the number of columns.
4. Greedy
Intuition
We can avoid sorting entirely by maintaining a sorted order implicitly. Track the column indices that had continuous 1s from the previous row. For the current row, first process columns from the previous list (they have taller heights), then add new columns that just started with a 1. This naturally keeps columns ordered by height in descending order.
Algorithm
- Maintain a list of column indices from the previous row that had continuous
1s. - For each row:
- Create a new list starting with columns from the previous list that still have a
1(incrementing their height in the matrix). - Append columns where the current cell is
1but was0before (new columns starting at height1). - Compute the maximum area: position
igives area(i + 1) * matrix[r][heights[i]].
- Create a new list starting with columns from the previous list that still have a
- Return the maximum area.
class Solution:
def largestSubmatrix(self, matrix: List[List[int]]) -> int:
ROWS, COLS = len(matrix), len(matrix[0])
res = 0
prevHeights = []
for r in range(ROWS):
heights = []
for c in prevHeights:
if matrix[r][c]:
matrix[r][c] += matrix[r - 1][c]
heights.append(c)
for c in range(COLS):
if matrix[r][c] == 1:
heights.append(c)
for i, c in enumerate(heights):
res = max(res, (i + 1) * matrix[r][c])
prevHeights = heights
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
Common Pitfalls
Resetting Heights on Zero Cells
When building the height array row by row, encountering a 0 should reset the height to 0 for that column. A common mistake is to simply add the previous height regardless of the current cell value, which incorrectly extends heights through 0 cells and produces invalid rectangles.
Forgetting Column Rearrangement is Allowed
Unlike the standard largest rectangle in histogram problem, columns can be rearranged here. Attempting to use a monotonic stack approach without sorting the heights misses the key insight. Sorting heights in descending order allows greedy computation of the maximum area at each width.
Incorrect Area Formula After Sorting
After sorting heights in descending order, the area at position i is (i + 1) * heights[i], not i * heights[i]. The width is the number of columns considered so far (1-indexed), so using i instead of i + 1 underestimates the width by one column.
Sign in to join the discussion