Start learning free

LESSON 247 OF 414 · PARTIAL SUMS #2

Count Submatrices with All Ones in Scala: Row Pairs and Runs of Columns

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
  • Every element of mat is either 0 or 1.
STEP 1 OF 13
    Ctrl + Enter to check

    SCALA · PARTIAL SUMS #2

    Count Submatrices With All Ones in Scala, explained

    Problem overview

    The method numberOfSubmatricesWithAllOnes receives a binary matrix mat with m rows and n columns, as an Array[Array[Int]]. It returns how many submatrices contain only ones. A submatrix is a rectangle of neighbouring cells, and rectangles in different positions count separately, even when they have the same size.

    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]] it is 24. The constraints allow up to 150 rows and 150 columns.

    Approach: fix the top and bottom rows

    Checking every rectangle of the matrix one by one is far too slow. A better way is to fix the vertical extent first. Pick a top row upRow and a bottom row i, with i at or below upRow. Every rectangle between those two rows is made of whole column segments, so only the columns matter now.

    A column is intact for this pair of rows if none of its cells from upRow to i is 0. A rectangle spanning exactly these rows is all ones exactly when all of its columns are intact. So the problem becomes one-dimensional: in a row of intact and broken columns, count the groups of neighbouring intact columns.

    A run of L consecutive intact columns holds L * (L + 1) / 2 such groups. The lesson counts them incrementally instead. Walk the columns left to right and track the current run length. At each column, the number of rectangles that use it as their right edge equals the length of the run that ends there: widths 1, 2, up to that length. Adding this run length at every column gives the total for the row pair.

    For the first example and the pair upRow = 0, i = 0, the intact columns are [1, 0, 1]. The run lengths are 1, 0, 1, and they add up to 2: the two single cells in that row.

    Step by step

    1. Create the counter matCount as a var starting at 0.
    2. Create fullCol with Array.fill(mat(0).length)(1): one entry per column, where 1 means "still intact".
    3. Loop over the top row upRow from 0 to the last row.
    4. Reset fullCol to all ones at the start of each top row. Breaks from a different top row must not carry over.
    5. Loop over the bottom row i from upRow to the last row.
    6. Loop over the columns with col, to refresh each column's status now that row i joined the slab.
    7. Break a column when it meets a zero: if mat(i)(col) == 0, set fullCol(col) = 0. Once a column is broken, it stays broken for every taller slab with the same top row, so the array is never set back to 1 inside the loop for i.
    8. Start a run counter: a var currLength = 0 before scanning the columns.
    9. Scan fullCol from left to right.
    10. Extend the run when the entry is 1, by incrementing currLength.
    11. Reset the run to 0 in the else branch, because a broken column ends it.
    12. Add currLength to matCount at every column. A run of length L ending here gives L new rectangles with this column as the right edge.
    13. Return matCount once all the loops are done.

    The solution in Scala

    class Solution {
        def numberOfSubmatricesWithAllOnes(mat: Array[Array[Int]]): Int = {
            var matCount = 0
            var fullCol = Array.fill(mat(0).length)(1)
    
            for (upRow <- 0 until mat.length) {
                fullCol = Array.fill(mat(0).length)(1)
    
                for (i <- upRow until mat.length) {
                    for (col <- 0 until mat(0).length) {
                        if (mat(i)(col) == 0) {
                            fullCol(col) = 0
                        }
                    }
    
                    var currLength = 0
                    for (col <- fullCol) {
                        if (col == 1) {
                            currLength += 1
                        } else {
                            currLength = 0
                        }
                        matCount += currLength
                    }
                }
            }
    
            return matCount
        }
    }
    

    The Scala details worth a closer look:

    • Array.fill(k)(1) creates an array of k ones. The first parameter list is the length, the second is the value.
    • The name col means two things. In the first inner loop, for (col <- 0 until mat(0).length), col is a column index. In the second, for (col <- fullCol) loops over the array's elements, so col is the value 0 or 1 and not an index. The two loops are separate, so there is no clash, but it is easy to misread.
    • mat(i)(col) reads row i, column col. Writing to an array element, as in fullCol(col) = 0, uses the same parentheses.
    • var fullCol is reassigned once per top row. Arrays are mutable, so a val and a loop that refills it would also work; the lesson builds a new array instead. The first Array.fill before the loop is only an initial value that the first iteration replaces.
    • The return. The lesson finishes with return matCount. Dropping the keyword and writing just matCount returns the same value, which is the more common Scala style.

    Complexity

    Time: O(m² · n). There are O(m²) pairs of top and bottom rows. For each pair, two passes over the n columns update the statuses and measure the runs. For a 150 by 150 matrix that is around 150 * 150 * 150 / 2 steps, a few million, which runs quickly.

    Space: O(n). The fullCol array has one entry per column. Each top row creates a new one, but the old one becomes garbage.

    Edge cases and common mistakes

    • All zeros. [[0, 0], [0, 0]] returns 0: every run has length 0.
    • All ones. [[1, 1], [1, 1]] returns 9: four 1x1, two 1x2, two 2x1 and one 2x2.
    • A single row. [[1, 1, 1, 1]] returns 10, which is 4 + 3 + 2 + 1. A single column [[1], [1], [1]] gives 6.
    • Forgetting to reset fullCol. A zero in an upper row would disqualify the column for slabs that start below it. The count would be too low.
    • Resetting currLength outside the scan. The run length must start at 0 for every scan of a new slab, so var currLength = 0 sits inside the loop for i.
    • Adding to matCount only when a run ends. The count would be one per run instead of one per rectangle width.
    • Using mat.length for the number of columns. That is the number of rows. The columns are mat(0).length.

    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, 0], [0, 0]] gives 0.
    • [[1, 1], [1, 1]] gives 9.
    • [[1, 1, 1, 1]] gives 10.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free