Problem overview
The function numberOfSubmatricesWithAllOnes receives an m x n matrix mat of 0s and 1s. It returns how many rectangular submatrices consist only of 1s. A submatrix is any block of consecutive rows and consecutive columns, so a single cell counts, and so does a 2x3 block.
For mat = [[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. The matrix has at most 150 rows and 150 columns.
Approach: fix the top and bottom rows
A submatrix is determined by its top row, bottom row, left column and right column. Trying all four choices would be far too slow. The trick is to fix only the two rows and count the column ranges quickly.
Pick a top row upRow and a bottom row downRow. Call a column intact if every cell in that column between those two rows is 1. A submatrix with this top and bottom is all ones exactly when each of its columns is intact. So the question becomes: how many ranges of consecutive columns consist only of intact columns?
A run of L consecutive intact columns contains L ranges ending at its last column, namely the ones of width 1, 2, ..., L. If you scan left to right keeping currLength, the length of the current run, and add currLength to the total at every column, you count each range once, at its right edge. A column that isn't intact resets currLength to 0.
The intact flags don't need recomputing from scratch. Keep the top row fixed and move the bottom row down one row at a time. Adding row downRow can only turn intact columns off, never on, so one pass over that row updates the flags.
Here is the first example with upRow = 0. With downRow = 0 the flags are [1, 0, 1], which gives runs of length 1, 0 and 1, so 2 submatrices. With downRow = 1 the flags become [1, 0, 0], giving 1. With downRow = 2 they stay [1, 0, 0], giving 1. That is 4 for this top row. The top rows 1 and 2 give 6 and 3, and the total is 13.
Step by step
- Create the counter.
matCountstarts at 0 and accumulates every submatrix found. - Create the flags.
fullColhas one entry per column, all set to 1. Entrycsays whether columncis still intact for the current rows.vector<int>(count, value)buildscountcopies ofvalue. - Loop over the top row with
upRowfrom the first row to the last. - Reset the flags at the start of each top row. A new top row begins a new slab, and flags switched off by earlier top rows would wrongly carry over.
- Loop over the bottom row with
downRowfromupRowto the last row. Starting atupRowincludes one-row slabs. - Visit every column of the row just added.
- Switch off the zero columns. If
mat[downRow][col]is 0, setfullCol[col]to 0. The flag stays off for every taller slab with the same top row. - Start a run counter. Declare
currLengthas 0 for each slab, after the flags are updated. - Scan the flags with a range-based loop. It yields the flag values, not column indices.
- Extend the run on a 1 by incrementing
currLength. - Break the run on a 0 by setting
currLengthto 0. - Add
currLengthtomatCountat every column, including the zero columns, where it adds 0. - Return
matCountafter all loops.
The solution in C++
class Solution {
public:
int numberOfSubmatricesWithAllOnes(vector<vector<int>>& mat) {
int matCount = 0;
vector<int> fullCol(mat[0].size(), 1);
for (int upRow = 0; upRow < mat.size(); upRow++) {
for (int i = 0; i < fullCol.size(); i++) {
fullCol[i] = 1;
}
for (int downRow = upRow; downRow < mat.size(); downRow++) {
for (int col = 0; col < mat[0].size(); col++) {
if (!mat[downRow][col]) {
fullCol[col] = 0;
}
}
int currLength = 0;
for (int col : fullCol) {
if (col == 1) {
currLength++;
} else {
currLength = 0;
}
matCount += currLength;
}
}
}
return matCount;
}
};
The C++ details worth knowing:
!mat[downRow][col]. The!operator turns anintinto abool: 0 becomes true and any other value becomes false. So the condition reads "this cell is 0".- Two different
colvariables. In the update loop,colis a column index. In the final range-based loop,int col : fullColis a copy of a flag value, 0 or 1. They live in separate scopes, so the code is valid, but the second name is easy to misread as an index. - Vector constructor.
vector<int> fullCol(mat[0].size(), 1)uses parentheses on purpose. With braces,{n, 1}would mean a vector holding the two numbersnand1. - Reference parameter.
vector<vector<int>>& matavoids copying the whole matrix. The solution only reads it. - Unsigned sizes. The loops compare an
intindex withsize(), which is unsigned. That is fine for non-negative indices, though the compiler may warn.
Complexity
Let the matrix have m rows and n columns.
Time: O(m² · n). There are about m²/2 pairs of (top row, bottom row). For each pair the code does two passes over n columns, one to update the flags and one to count runs. With m = n = 150 that is about 3.4 million column steps, well within the limits.
Space: O(n). The only extra memory is the fullCol vector.
The answer itself is at most (150 · 151 / 2)² = 128,255,625, which fits in an int. This is the count for a matrix of all ones.
Edge cases and common mistakes
- A single cell.
[[1]]gives 1 and[[0]]gives 0. - All ones. A 2x2 block of 1s gives 9: four 1x1, two 1x2, two 2x1 and one 2x2.
- A single row.
[[1, 1, 1]]gives 6, which is 1 + 2 + 3. - Not resetting
fullColfor each top row. Zeros found under an earlier top row would remove columns that are intact for the new top row, and the count would be too low. - Not resetting
currLengthfor each slab. A run would leak from one slab into the next. It must be declared inside thedownRowloop. - Adding only at the end of a run. Adding
currLengthat every column, not once per run, is what counts the shorter ranges inside it. - Starting
downRowatupRow + 1. That skips all submatrices that are one row tall.
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]]gives9.[[1,1,1]]gives6.[[0]]gives0.