Problem overview
NumberOfSubmatricesWithAllOnes receives a binary matrix as a jagged array, int[][] mat, and returns how many rectangular submatrices contain only 1s. Two rectangles count separately if their position differs, even when they cover the same pattern. A single cell with a 1 is a submatrix, and the matrix has between 1 and 150 rows and columns.
For [[1,0,1],[1,1,0],[1,1,0]] the answer is 13: six 1x1 rectangles, two 1x2, three 2x1, one 2x2 and one 3x1. For [[0,1,1,0],[0,1,1,1],[1,1,1,0]] the answer is 24.
Approach
A rectangle is fixed by its top row, bottom row, left column and right column. Trying all four choices with a check of the cells inside would be far too slow. The solution fixes the top and bottom rows first, which gives a horizontal slab, and counts the rectangles inside that slab in one scan.
Inside a slab, a column is intact if every cell of that column within the slab is 1. A rectangle with exactly those top and bottom rows is all ones precisely when all its columns are intact. So the rectangles in the slab are the contiguous stretches of intact columns.
The key observation is how to count them. Scan the columns left to right and track currLength, the length of the run of intact columns that ends at the current column. If the run has length L, then L rectangles use the current column as their right edge, namely the ones of widths 1, 2, ..., L. Adding currLength to the total at every column therefore counts every rectangle in the slab exactly once. A broken column resets the run to 0 and adds nothing.
Growing the slab
For a fixed top row, the bottom row moves down one row at a time. Making the slab one row taller can only break columns, never repair them, so the array fullCol is updated in place: a 0 in the new row sets that column's entry to 0. No rescan of the earlier rows is needed. When the top row changes, fullCol is refilled with 1s.
On the first example, the slab from row 0 to row 0 has intact columns [1, 0, 1], which adds 2. Rows 0 to 1 leave only column 0 intact, adding 1, and rows 0 to 2 add 1 again. That is 4 for top row 0. Top row 1 contributes 3 + 3 = 6, and top row 2 contributes 3. The total is 4 + 6 + 3 = 13.
Step by step
- Declare
matCountas the running total. - Create
fullColas anint[]with one entry per column, filled with 1s usingArray.Fill. - Loop over the top row,
upRow, from 0 to the last row. - Refill
fullColwith 1s at the start of each top row, so earlier slabs don't disqualify columns in the new one. - Loop the bottom row,
i, fromupRowdown to the last row. - Update the columns. For each column, if
mat[i][col]is 0, store 0 infullCol[col]. - Start a fresh
currLengthat 0 for every slab, then scanfullColfrom left to right. - Extend or reset the run: add 1 to
currLengthfor an intact column, or set it to 0 otherwise. - Add
currLengthtomatCounton every column, then returnmatCountafter the loops.
The solution in C#
class Solution {
public int NumberOfSubmatricesWithAllOnes(int[][] mat) {
int matCount = 0;
int[] fullCol = new int[mat[0].Length];
Array.Fill(fullCol, 1);
for (int upRow = 0; upRow < mat.Length; upRow++) {
Array.Fill(fullCol, 1);
for (int i = upRow; i < mat.Length; i++) {
for (int col = 0; col < mat[0].Length; col++) {
if (mat[i][col] == 0) {
fullCol[col] = 0;
}
}
int currLength = 0;
foreach (int col in fullCol) {
if (col == 1) {
currLength++;
} else {
currLength = 0;
}
matCount += currLength;
}
}
}
return matCount;
}
}
C# details:
- Jagged arrays.
int[][]is an array of row arrays, somat.Lengthis the number of rows,mat[0].Lengthis the number of columns, and a cell ismat[i][col]. This differs from the two-dimensionalint[,], which usesgrid[x, y]. Array.Fill(array, value)sets every element of an array to the same value. It is a static method ofSystem.Array, available since .NET Core 2.0.- Two variables named
col. Theforloop'scolis an index, and theforeachloop'scolis a value fromfullCol. The two loops are siblings, so each variable exists only inside its own loop and they don't clash. Using the same name for both is confusing, though, so a name such asflagfor the second would read better. foreachreads, it doesn't write. You can't assign to the loop variable, which is fine here becauseforeachonly readsfullCol.
Complexity
Time: O(R² x C), where R is the number of rows and C the number of columns. There are about R²/2 (top, bottom) pairs, and each does O(C) work for the update and again for the scan. With 150 x 150 inputs, that is a few million steps.
Space: O(C). Only fullCol is allocated, one entry per column.
Edge cases and common mistakes
- Forgetting to reset
fullColwhenupRowchanges. Zeros from an earlier slab then wrongly disqualify columns, and the count comes out too small. - Not resetting
currLengthper slab. A run must not carry over from one slab to the next. The declaration sits inside theiloop for this reason. - Adding only at the end of a run. Adding
currLengthat every column counts all rectangle widths. Adding once per run would count only the widest. - A matrix of all zeros returns 0, and a single
[[1]]returns 1. - An all-ones matrix with R rows and C columns has R(R+1)/2 x C(C+1)/2 submatrices. For 2 rows and 3 columns that is 3 x 6 = 18, which the code returns.
- Counting with
int. The largest case, a 150 x 150 matrix of ones, has (150 x 151 / 2)² = 128,255,625 submatrices, which fits in anint.
Testing
[[1,0,1],[1,1,0],[1,1,0]]gives13.[[0,1,1,0],[0,1,1,1],[1,1,1,0]]gives24.[[1,1,1],[1,1,1]]gives18.