1727. Largest Submatrix With Rearrangements
Problem Description
You have a binary matrix (containing only 0s and 1s) with dimensions m x n
. You can rearrange the columns of this matrix in any order you want.
Your goal is to find the largest possible rectangular area that contains only 1s after optimally rearranging the columns.
For example, if you have a matrix where some columns have consecutive 1s, you can rearrange these columns to be adjacent to each other, potentially creating a larger rectangle of 1s than what existed in the original arrangement.
The problem asks you to:
- Consider all possible ways to rearrange the columns
- Find the arrangement that gives you the largest rectangular submatrix containing only 1s
- Return the area of that largest submatrix
Note that you can only rearrange entire columns - you cannot rearrange individual elements or rows. Each column moves as a complete unit when rearranged.
Intuition
The key insight is that when we can rearrange columns freely, we want to group columns with consecutive 1s together to form larger rectangles.
Let's think about what happens when we look at each row as a potential bottom edge of our rectangle. For any row, if we know the height of consecutive 1s extending upward from each position, we can determine the best rectangle with that row as the base.
First observation: If we transform each cell matrix[i][j]
that contains a 1 to represent the number of consecutive 1s above it (including itself), we get valuable height information. For example, if matrix[i][j] = 3
, it means there are 3 consecutive 1s from row i
going upward in column j
.
Second observation: Once we have these heights for a particular row, we can rearrange the columns optimally. How? By sorting the heights in descending order!
Why does sorting work? Consider a row with heights [3, 1, 2, 4]
. After sorting in descending order: [4, 3, 2, 1]
. Now:
- Using just the first column (height 4), we get area =
4 ร 1 = 4
- Using the first two columns (minimum height 3), we get area =
3 ร 2 = 6
- Using the first three columns (minimum height 2), we get area =
2 ร 3 = 6
- Using all four columns (minimum height 1), we get area =
1 ร 4 = 4
By sorting, we ensure that when we consider k
columns, we're taking the k
tallest columns available, maximizing our potential area. The area with k
columns is min_height ร k
, and sorting guarantees we're always working with the best possible minimum height for any given width.
This approach cleverly sidesteps the need to explicitly try all column permutations by recognizing that for each row, the optimal arrangement is simply to sort by height.
Solution Approach
The implementation consists of two main phases: preprocessing and calculating the maximum area.
Phase 1: Preprocessing - Building Height Matrix
We iterate through the matrix starting from the second row (index 1) and update each cell that contains a 1:
for i in range(1, len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j]:
matrix[i][j] = matrix[i - 1][j] + 1
This transforms each cell matrix[i][j]
from a binary value to the count of consecutive 1s extending upward from that position. If matrix[i][j]
is 0, it remains 0. If it's 1, it becomes matrix[i-1][j] + 1
, accumulating the height of the column of 1s.
For example, a column [1, 1, 1, 0, 1]
becomes [1, 2, 3, 0, 1]
after preprocessing.
Phase 2: Finding Maximum Area
For each row in the preprocessed matrix:
for row in matrix:
row.sort(reverse=True)
for j, v in enumerate(row, 1):
ans = max(ans, j * v)
-
Sort each row in descending order: This rearranges the columns optimally for that row. The tallest columns come first.
-
Calculate potential areas: For each position
j
(1-indexed) with heightv
:- The area is
j * v
because:- We're considering the first
j
columns (after sorting) - The minimum height among these
j
columns isv
(since they're sorted) - Rectangle area = width ร height =
j ร v
- We're considering the first
- The area is
-
Track the maximum: We update
ans
with the maximum area found across all rows and all possible rectangle widths.
The algorithm efficiently explores all possible rectangles by:
- Using each row as a potential bottom edge
- Leveraging sorting to automatically find the optimal column arrangement for each row
- Computing areas for all possible widths in the sorted arrangement
Time complexity: O(m ร n ร log n)
where m
is the number of rows and n
is the number of columns (due to sorting each row).
Space complexity: O(1)
extra space as we modify the matrix in place.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider this 3ร4 binary matrix:
1 0 1 0 1 1 1 1 1 1 0 1
Phase 1: Building the Height Matrix
We transform each cell to represent the count of consecutive 1s extending upward from that position.
Starting with row 1 (index 1):
- Position [1][0]: has a 1, and above it is also 1, so it becomes 1 + 1 = 2
- Position [1][1]: has a 1, but above it is 0, so it stays 1
- Position [1][2]: has a 1, and above it is also 1, so it becomes 1 + 1 = 2
- Position [1][3]: has a 1, but above it is 0, so it stays 1
After processing row 1:
1 0 1 0 2 1 2 1 1 1 0 1
Processing row 2 (index 2):
- Position [2][0]: has a 1, and above it is 2, so it becomes 2 + 1 = 3
- Position [2][1]: has a 1, and above it is 1, so it becomes 1 + 1 = 2
- Position [2][2]: has a 0, so it stays 0
- Position [2][3]: has a 1, and above it is 1, so it becomes 1 + 1 = 2
Final height matrix:
1 0 1 0 2 1 2 1 3 2 0 2
Phase 2: Finding Maximum Area
Now we process each row, sort it in descending order, and calculate possible areas.
Row 0: [1, 0, 1, 0]
- After sorting:
[1, 1, 0, 0]
- Position 1: width=1, height=1, area = 1ร1 = 1
- Position 2: width=2, height=1, area = 2ร1 = 2
- Position 3: width=3, height=0, area = 3ร0 = 0
- Position 4: width=4, height=0, area = 4ร0 = 0
- Maximum for this row: 2
Row 1: [2, 1, 2, 1]
- After sorting:
[2, 2, 1, 1]
- Position 1: width=1, height=2, area = 1ร2 = 2
- Position 2: width=2, height=2, area = 2ร2 = 4
- Position 3: width=3, height=1, area = 3ร1 = 3
- Position 4: width=4, height=1, area = 4ร1 = 4
- Maximum for this row: 4
Row 2: [3, 2, 0, 2]
- After sorting:
[3, 2, 2, 0]
- Position 1: width=1, height=3, area = 1ร3 = 3
- Position 2: width=2, height=2, area = 2ร2 = 4
- Position 3: width=3, height=2, area = 3ร2 = 6
- Position 4: width=4, height=0, area = 4ร0 = 0
- Maximum for this row: 6
Final Answer: 6
The largest rectangular area of 1s is 6, achieved by using row 2 as the bottom edge and rearranging columns to place the three tallest columns (heights 3, 2, 2) adjacent to each other, forming a 3ร2 rectangle.
Solution Implementation
1class Solution:
2 def largestSubmatrix(self, matrix: List[List[int]]) -> int:
3 # Build consecutive ones height for each column
4 # For each cell, if it's 1, accumulate the height from the cell above
5 for row in range(1, len(matrix)):
6 for col in range(len(matrix[0])):
7 if matrix[row][col] == 1:
8 # Add the consecutive ones from the row above
9 matrix[row][col] = matrix[row - 1][col] + 1
10
11 max_area = 0
12
13 # For each row, find the maximum rectangle area
14 for row in matrix:
15 # Sort heights in descending order to form valid rectangles
16 # After sorting, position j can form a rectangle with width j
17 row.sort(reverse=True)
18
19 # Calculate area for each possible width
20 # Width j (1-indexed) with height row[j-1] forms a rectangle
21 for width, height in enumerate(row, start=1):
22 current_area = width * height
23 max_area = max(max_area, current_area)
24
25 return max_area
26
1class Solution {
2 public int largestSubmatrix(int[][] matrix) {
3 int numRows = matrix.length;
4 int numCols = matrix[0].length;
5
6 // Transform the matrix to store consecutive 1s count from top
7 // For each cell with value 1, update it to be the count of consecutive 1s above it plus itself
8 for (int row = 1; row < numRows; row++) {
9 for (int col = 0; col < numCols; col++) {
10 if (matrix[row][col] == 1) {
11 // Add the count of consecutive 1s from the cell above
12 matrix[row][col] = matrix[row - 1][col] + 1;
13 }
14 }
15 }
16
17 int maxArea = 0;
18
19 // For each row, find the maximum rectangle area that can be formed
20 for (int[] currentRow : matrix) {
21 // Sort the row to group heights together in ascending order
22 Arrays.sort(currentRow);
23
24 // Calculate the maximum rectangle area for this row
25 // Start from the tallest height (rightmost after sorting)
26 int width = 1;
27 for (int col = numCols - 1; col >= 0 && currentRow[col] > 0; col--) {
28 // Calculate area: height * width
29 // Height is currentRow[col], width increases as we move left
30 int currentArea = currentRow[col] * width;
31 maxArea = Math.max(maxArea, currentArea);
32 width++;
33 }
34 }
35
36 return maxArea;
37 }
38}
39
1class Solution {
2public:
3 int largestSubmatrix(vector<vector<int>>& matrix) {
4 // Get matrix dimensions
5 int numRows = matrix.size();
6 int numCols = matrix[0].size();
7
8 // Transform matrix: each cell stores the consecutive 1s above it (including itself)
9 // This creates a histogram representation for each row
10 for (int row = 1; row < numRows; ++row) {
11 for (int col = 0; col < numCols; ++col) {
12 if (matrix[row][col] == 1) {
13 // Add the height of consecutive 1s from the row above
14 matrix[row][col] = matrix[row - 1][col] + 1;
15 }
16 }
17 }
18
19 // Find the maximum area rectangle
20 int maxArea = 0;
21
22 // Process each row to find the largest rectangle ending at that row
23 for (auto& currentRow : matrix) {
24 // Sort heights in descending order to maximize rectangle area
25 // Larger heights come first, allowing us to form wider rectangles
26 sort(currentRow.rbegin(), currentRow.rend());
27
28 // Calculate the maximum area for rectangles in this row
29 // Width is (col + 1), height is the minimum height up to this column
30 for (int col = 0; col < numCols; ++col) {
31 int currentArea = (col + 1) * currentRow[col];
32 maxArea = max(maxArea, currentArea);
33 }
34 }
35
36 return maxArea;
37 }
38};
39
1/**
2 * Finds the largest submatrix area containing only 1s after column rearrangement
3 * @param matrix - 2D binary matrix containing 0s and 1s
4 * @returns The area of the largest submatrix
5 */
6function largestSubmatrix(matrix: number[][]): number {
7 const rowCount: number = matrix.length;
8 const columnCount: number = matrix[0].length;
9
10 // Transform matrix: replace 1s with consecutive 1s count from current position downward
11 for (let col: number = 0; col < columnCount; col++) {
12 for (let row: number = 0; row < rowCount; row++) {
13 // Count consecutive 1s starting from current position
14 let currentRow: number = row;
15 let consecutiveOnes: number = 0;
16
17 // Count consecutive 1s downward from current position
18 while (currentRow < rowCount && matrix[currentRow][col] === 1) {
19 consecutiveOnes++;
20 currentRow++;
21 }
22
23 // Fill the counted values in descending order
24 while (consecutiveOnes !== 0) {
25 matrix[row][col] = consecutiveOnes;
26 consecutiveOnes--;
27 row++;
28 }
29 }
30 }
31
32 // Sort each row in ascending order to optimize rectangle formation
33 for (let row: number = 0; row < rowCount; row++) {
34 matrix[row].sort((a: number, b: number) => a - b);
35 }
36
37 // Calculate maximum submatrix area
38 let maxArea: number = 0;
39
40 // For each row, calculate possible rectangle areas
41 for (let row: number = 0; row < rowCount; row++) {
42 // Iterate from right to left (largest heights first due to sorting)
43 for (let col: number = columnCount - 1; col >= 0; col--) {
44 // Height is the value at current position
45 // Width is the number of columns from current to end
46 const height: number = matrix[row][col];
47 const width: number = columnCount - col;
48 const currentArea: number = height * width;
49
50 maxArea = Math.max(maxArea, currentArea);
51 }
52 }
53
54 return maxArea;
55}
56
Time and Space Complexity
Time Complexity: O(m ร n ร log n)
where m
is the number of rows and n
is the number of columns in the matrix.
- The first nested loop runs through all elements of the matrix once to calculate consecutive heights:
O(m ร n)
- The second part iterates through each row (
m
rows) and sorts each row which takesO(n log n)
time - After sorting, we iterate through each sorted row once:
O(n)
per row - Total for the second part:
O(m ร (n log n + n))
=O(m ร n ร log n)
- Overall time complexity:
O(m ร n) + O(m ร n ร log n)
=O(m ร n ร log n)
(dominated by the sorting operation)
Space Complexity: O(1)
if we don't count the modification of the input matrix, or O(m ร n)
if we consider the input matrix modification as extra space.
- The algorithm modifies the input matrix in-place to store the consecutive heights
- The sorting operation uses
O(log n)
space for the sorting algorithm's recursion stack (Python's Timsort) - No additional data structures are created apart from a few variables (
ans
,i
,j
,v
) - If modifying the input is not allowed and we need to preserve the original matrix, we would need
O(m ร n)
extra space to create a copy
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Sorting Logic
The Problem: A common mistake is thinking that sorting the heights in descending order arbitrarily rearranges values. In reality, sorting represents the optimal rearrangement of columns for maximizing rectangular areas at each row level.
Why This Happens:
When we sort row = [3, 0, 2, 1]
to get [3, 2, 1, 0]
, we're not just sorting numbers - we're rearranging entire columns based on their heights at this row. The confusion arises because after preprocessing, each value represents a column's height, not just a single cell.
Solution: Remember that each value after preprocessing represents the height of consecutive 1s in that column up to the current row. Sorting these heights allows us to group the tallest columns together, which maximizes potential rectangle areas.
Pitfall 2: Incorrect Area Calculation After Sorting
The Problem:
Developers sometimes calculate the area as height * original_width
or use incorrect indexing like row[j] * (j+1)
when j is 0-indexed.
Example of Wrong Implementation:
for j in range(len(row)):
area = row[j] * (j + 1) # Wrong when j is 0-indexed
Why This Fails:
After sorting in descending order, the width of a valid rectangle at position j (0-indexed) is actually j+1
because we can use columns from index 0 to j inclusive. The height is limited by the minimum height in this range, which is row[j]
due to descending order.
Solution: Use 1-indexed enumeration for clarity:
for width, height in enumerate(row, start=1):
area = width * height
Pitfall 3: Not Handling Edge Cases
The Problem: The algorithm might fail or give incorrect results for:
- Empty matrices
- Single row or single column matrices
- Matrices with all 0s
Why This Happens: The preprocessing loop starts from index 1, which assumes at least 2 rows exist. For single-row matrices, the preprocessing loop never executes, but this is actually correct behavior since no accumulation is needed.
Solution: The current implementation actually handles these cases correctly:
- Single row: Preprocessing is skipped, sorting still works
- All 0s: Heights remain 0, maximum area is 0
- Empty matrix: Returns 0 (though checking for empty input is good practice)
For extra safety, you could add:
if not matrix or not matrix[0]: return 0
Pitfall 4: Modifying the Input Matrix
The Problem: The solution modifies the input matrix in-place during preprocessing. This could be unexpected behavior if the caller needs the original matrix preserved.
Why This Matters: In interview settings or production code, modifying input parameters without documentation can lead to bugs in calling code that expects the matrix to remain unchanged.
Solution: Either:
- Document that the function modifies the input
- Create a copy of the matrix:
matrix = [row[:] for row in matrix] # Create a deep copy
- Use a separate height matrix:
heights = [[0] * len(matrix[0]) for _ in range(len(matrix))]
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!