Start learning free

LESSON 222 OF 338 · PARTIAL SUMS #2

Count Submatrices With All Ones in Go: Row Pairs and Runs, O(m² * n)

Practice O(n^3) Read the full explanation

Given an m x n binary matrix mat, return the number of submatrices that have all ones.

 

Example 1:

Input: mat = [[1,0,1],[1,1,0],[1,1,0]]
Output: 13
Explanation: 
There are 6 rectangles of side 1x1.
There are 2 rectangles of side 1x2.
There are 3 rectangles of side 2x1.
There is 1 rectangle of side 2x2. 
There is 1 rectangle of side 3x1.
Total number of rectangles = 6 + 2 + 3 + 1 + 1 = 13.

Example 2:

Input: mat = [[0,1,1,0],[0,1,1,1],[1,1,1,0]]
Output: 24
Explanation: 
There are 8 rectangles of side 1x1.
There are 5 rectangles of side 1x2.
There are 2 rectangles of side 1x3. 
There are 4 rectangles of side 2x1.
There are 2 rectangles of side 2x2. 
There are 2 rectangles of side 3x1. 
There is 1 rectangle of side 3x2. 
Total number of rectangles = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24.

 

Constraints:

  • 1 <= m, n <= 150
  • mat[i][j] is either 0 or 1.
STEP 1 OF 13
    Ctrl + Enter to check

    GO · PARTIAL SUMS #2

    Count Submatrices With All Ones in Go, explained

    Problem overview

    The function numberOfSubmatricesWithAllOnes receives a binary matrix mat with m rows and n columns, made of 0s and 1s. It returns how many rectangular submatrices contain only 1s. Rectangles of every size count, including single cells, and two rectangles that cover different cells are different even if they have the same shape.

    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.

    Approach: fix the top and bottom rows, then count along the columns

    Checking every rectangle directly means choosing four boundaries and then verifying the cells inside, which is far too slow for a 150 x 150 matrix. The lesson cuts the work down in two moves.

    First, fix a vertical slab. Pick a top row upRow and a bottom row downRow. Every rectangle that spans exactly those rows differs only in its left and right columns. A column can belong to such a rectangle only if all its cells between the two rows are 1. Call such a column intact.

    Second, count the rectangles from the intact columns. A rectangle in the slab is a block of consecutive intact columns. If you scan the columns left to right and track the length currLength of the current run of intact columns, then a run of length L that ends at this column gives L rectangles with this column as the right edge: widths 1, 2, up to L. Adding currLength at every column therefore counts every rectangle in the slab exactly once, by its right edge.

    Moving downRow down one row at a time, the intact columns can only shrink: a 0 in the new row breaks its column for this slab and for every taller slab with the same top. So the column status is updated in place instead of recomputed.

    On the example, take the slab of rows 0 to 1. Column 0 has 1 and 1, column 1 has 0 and 1, column 2 has 1 and 0. Only column 0 is intact, so the run is 1 long and the slab contributes 1 rectangle: the 2x1 block in column 0.

    Step by step

    1. Create the counter matCount, set to 0. It collects every slab's contribution.
    2. Allocate fullCol with make, one entry per column. Each entry will say whether that column is still intact in the current slab.
    3. Loop over the top row upRow, so that every possible top edge is considered.
    4. Reset fullCol to all ones at the start of each top row. A new slab starts with no column disqualified, and leftover zeros from an earlier top row would be wrong.
    5. Loop over the bottom row downRow, from upRow to the last row, so the slab grows one row at a time.
    6. Update every column with the new bottom row: when mat[downRow][col] is 0, set that column's entry to 0.
    7. Start a run counter currLength at 0 for the left-to-right scan.
    8. Scan fullCol with a range loop. An intact column extends the run; a broken one resets it to 0.
    9. Add currLength to matCount at every column, which counts the rectangles ending there. Adding 0 for a broken column is harmless.
    10. Return matCount after the loops.

    The solution in Go

    func numberOfSubmatricesWithAllOnes(mat [][]int) int {
        matCount := 0
        fullCol := make([]int, len(mat[0]))
    
        for upRow := 0; upRow < len(mat); upRow++ {
            for i := 0; i < len(fullCol); i++ {
                fullCol[i] = 1
            }
    
            for downRow := upRow; downRow < len(mat); downRow++ {
                for col := 0; col < len(mat[0]); col++ {
                    if mat[downRow][col] == 0 {
                        fullCol[col] = 0
                    }
                }
    
                currLength := 0
                for _, col := range fullCol {
                    if col == 1 {
                        currLength++
                    } else {
                        currLength = 0
                    }
                    matCount += currLength
                }
            }
        }
    
        return matCount
    }
    

    The Go details worth a closer look:

    • make([]int, n) creates a slice of length n filled with zeros. That is why the reset loop is needed before first use: the zero value is the opposite of the "intact" marker 1.
    • range yields index and value. for _, col := range fullCol discards the index with the blank identifier _, because Go rejects an unused variable. Here col is the value (0 or 1), not a column number, unlike the col in the earlier loop. The two scopes are separate, but it is a naming trap when reading the code.
    • The reset is a loop. A slice has no fill method, so the code assigns each element.
    • Row and column counts. len(mat) is the number of rows and len(mat[0]) the number of columns. The code assumes at least one row, which the constraints guarantee.
    • No ternary, no while. The run counter uses a plain if/else, and all loops are for.

    Complexity

    Time: O(m² * n). There are about m²/2 pairs of (upRow, downRow), and each does two passes over the n columns: the update and the scan. With m = n = 150 that is about 11,000 pairs times 150 columns, under two million steps per pass.

    Space: O(n). Only fullCol is allocated, one entry per column.

    Edge cases and common mistakes

    • All zeros. [[0]] returns 0.
    • A single 1. [[1]] returns 1.
    • One row of ones. [[1,1,1]] returns 6: three of width 1, two of width 2 and one of width 3. This is the 1 + 2 + 3 pattern the run counter produces.
    • A full square. [[1,1],[1,1]] returns 9.
    • Forgetting the reset of fullCol when upRow changes. A zero from the previous top row would stay and hide rectangles.
    • Adding currLength only at the end of a run. Every column in a run is the right edge of some rectangles, so the addition belongs inside the loop, after the if/else.
    • Counting rectangles by shape. The answer counts positions, not distinct sizes, so the six 1x1 rectangles in the example all count.

    Testing

    • [[1,0,1],[1,1,0],[1,1,0]] gives 13.
    • [[0,1,1,0],[0,1,1,1],[1,1,1,0]] gives 24.
    • [[0]] gives 0.
    • [[1,1,1]] gives 6.
    • [[1,1],[1,1]] gives 9.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free