Problem overview
The method numberOfSubmatricesWithAllOnes receives a binary matrix mat with m rows and n columns. It returns how many rectangular submatrices consist entirely of 1s. A submatrix is any block of consecutive rows and consecutive columns, and a single cell counts as a 1x1 submatrix.
For [[1, 0, 1], [1, 1, 0], [1, 1, 0]] the answer is 13. There are six 1x1 blocks (one per 1), two 1x2 blocks, three 2x1 blocks, one 2x2 block and one 3x1 block. Rectangles of different positions are counted separately, even when they have the same shape.
Approach: fix the rows, then count along the columns
Choosing a rectangle means choosing a top row, a bottom row, a left column and a right column. Trying all four choices directly is too slow, so the lesson fixes the first two and counts the last two cleverly.
Fix a top row upRow and a bottom row downRow. The rows between them form a horizontal slab. A column is full for this slab if every cell of it inside the slab is 1. A rectangle in the slab with all ones must use only full columns, and any run of consecutive full columns gives all the rectangles you can cut from it.
So the problem inside one slab is a one-dimensional count: given a row of full/not-full flags, how many runs of consecutive full flags are there, counting every sub-run? A run of length L holds L * (L + 1) / 2 of them. The lesson counts them by scanning left to right. At each column, if the run of full columns ending there has length currLength, then exactly currLength rectangles end at that column: widths 1, 2, up to currLength. Add that to the total at every column.
Moving downRow down by one row only adds one row to the slab, so the full-column flags can be updated in place. A column can only lose its status as the slab grows, which makes the update a single pass over the columns.
Step by step
- Start the total.
matCountbegins at 0. - Create
fullColas a list of 1s, one per column:[1] * len(mat[0]). Entrycsays whether columncis still all ones in the current slab. - Loop over
upRowfrom the first row to the last. It fixes the slab's top edge. - Reset
fullColto 1s at the start of each top row with a small loop overi. A new slab starts with no disqualified columns. Without the reset, zeros from the previous top row would leak into it. - Loop over
downRowfromupRowto the last row. The slab now spansupRowtodownRow. - Loop over each column
colto fold in the row just added. - Disqualify columns that hit a zero. If
mat[downRow][col]is 0, setfullCol[col]to 0. It never goes back to 1 while the slab grows. - Start a run counter:
currLength = 0, after the update loop. - Loop over
fullColitself. The loop variable, namedcolagain, now holds the flag (0 or 1), not an index. - Extend the run on a 1: add 1 to
currLength. - Reset the run on a 0: set
currLengthto 0. - Add
currLengthtomatCountat every column, inside the same loop. These are the rectangles ending at that column for this slab. - Return
matCountafter all loops.
The solution in Python
class Solution:
def numberOfSubmatricesWithAllOnes(self, mat):
matCount = 0
fullCol = [1] * len(mat[0])
for upRow in range(len(mat)):
for i in range(len(fullCol)):
fullCol[i] = 1
for downRow in range(upRow, len(mat)):
for col in range(len(mat[0])):
if not mat[downRow][col]:
fullCol[col] = 0
currLength = 0
for col in fullCol:
if col == 1:
currLength += 1
else:
currLength = 0
matCount += currLength
return matCount
Here is the slab starting at row 0 for the first example. After row 0, fullCol is [1, 0, 1], which adds 1 and 0 and 1, so 2 (two 1x1 blocks). After row 1 it is [1, 0, 0], adding 1 (a 2x1 block). After row 2 it is still [1, 0, 0], adding 1 (a 3x1 block).
Python details worth knowing:
[1] * nbuilds a list ofnones. This is safe for numbers. With lists inside, the copies would all be the same object.not mat[downRow][col]is true when the cell is 0, becausenottreats 0 as false.- The reused name
col. The first loop usescolas an index and the second as a value fromfullCol. Python allows it because the loops are separate, but it can confuse readers. - Resetting in place. The loop with
iwrites 1s into the same list, so there is only onefullCol. Assigning a new list would do the same.
Complexity
Time: O(m² · n). The two row loops visit about m² / 2 slabs. For each, two passes over the n columns do constant work per column. With m and n up to 150, that is roughly 150 · 150 · 150 operations, around three million.
Space: O(n). The only extra storage is fullCol.
Edge cases and common mistakes
- Single cell.
[[1]]gives 1 and[[0]]gives 0. - Full matrix.
[[1, 1], [1, 1]]gives 9: four 1x1, two 1x2, two 2x1 and one 2x2. - One row.
[[1, 1, 1]]gives 6, the run-counting formula3 * 4 / 2. - Forgetting the reset. Without resetting
fullColfor eachupRow, later top rows start with columns already marked as broken, and the count comes out too low. - Not resetting
currLengthper slab. The counter must restart at 0 for eachdownRow, since runs don't carry over between slabs. - Adding only at the end of a run. The sum must be taken at every column, not once per run, otherwise the shorter widths are lost.
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.