5 Best Ways to Program to Count Number of Square Submatrices in a Given Binary Matrix in Python

πŸ’‘ Problem Formulation: The challenge is to write a program that can efficiently count the total number of square submatrices that have all ones in a binary matrix. Given a 2D binary matrix filled with 0’s and 1’s, the goal is to determine the count of square submatrices with all 1’s. For instance, the input [[1,0,1],[1,1,0],[1,1,0]] should return an output of 7.

Method 1: Dynamic Programming

This approach uses dynamic programming to build a solution incrementally. The idea is to traverse the matrix while keeping track of the size of the largest square submatrix ending at each position that only includes ones. This method is efficient and avoids redundant computations.

Here’s an example:

def countSquares(matrix):
    rows, cols = len(matrix), len(matrix[0])
    count = 0
    for r in range(rows):
        for c in range(cols):
            if matrix[r][c] == 1:
                if r == 0 or c == 0:
                    count += 1
                else:
                    cell_val = min(matrix[r-1][c], matrix[r][c-1], matrix[r-1][c-1]) + matrix[r][c]
                    count += cell_val
                    matrix[r][c] = cell_val
    return count
    
matrix = [[1,0,1],[1,1,0],[1,1,0]]
print(countSquares(matrix))

Output: 7

This code snippet defines a function countSquares() that accepts a binary matrix and returns the count of square submatrices with all 1’s. The dynamic programming solution iteratively updates the matrix in place, and the count is incremented accordingly.

Method 2: Brute Force with Optimization

This brute force method checks every possible square submatrix but uses optimizations to skip unnecessary checks. Submatrices are considered only if their starting cell is ‘1’, and further checks are done to confirm all member cells are ‘1’. Despite its simplicity, this method is less efficient than dynamic programming for larger matrices.

Here’s an example:

def isSquareAllOnes(matrix, top, left, size):
    for r in range(top, top + size):
        for c in range(left, left + size):
            if matrix[r][c] == 0:
                return False
    return True

def countSquares(matrix):
    rows, cols = len(matrix), len(matrix[0])
    count = 0
    maxSize = min(rows, cols)
    for r in range(rows):
        for c in range(cols):
            if matrix[r][c] == 1:
                for size in range(1, maxSize+1):
                    if r+size <= rows and c+size <= cols and isSquareAllOnes(matrix, r, c, size):
                        count += 1
                    else:
                        break
    return count
    
matrix = [[1,0,1],[1,1,0],[1,1,0]]
print(countSquares(matrix))

Output: 7

The function isSquareAllOnes() checks whether a square submatrix all contains ones, and countSquares() applies this check iteratively while optimizing size checks.

Method 3: Depth-First Search (DFS)

The DFS approach recursively explores each potential submatrix starting from each ‘1’ in the matrix. The search expands while consecutive ones are found, counting each valid square submatrix. This method can be intuitive but may be more cumbersome for larger matrices due to recursive calls leading to potential stack overflows.

Here’s an example:

def dfs(matrix, row, col, rows, cols):
    if row >= rows or col >= cols or matrix[row][col] == 0:
        return 0
    down = dfs(matrix, row+1, col, rows, cols)
    right = dfs(matrix, row, col+1, rows, cols)
    diag = dfs(matrix, row+1, col+1, rows, cols)
    return 1 + min(down, right, diag)

def countSquares(matrix):
    rows, cols = len(matrix), len(matrix[0])
    count = 0
    for r in range(rows):
        for c in range(cols):
            if matrix[r][c] == 1:
                count += dfs(matrix, r, c, rows, cols)
    return count
    
matrix = [[1,0,1],[1,1,0],[1,1,0]]
print(countSquares(matrix))

Output: 7

This example uses the depth-first search algorithm in a function dfs() to explore and count square submatrices starting with ‘1’s, while countSquares() calls dfs() for each cell in the matrix.

Method 4: Breadth-First Search (BFS)

BFS uses a queue to iteratively explore levels of potential square submatrices, layer by layer. Starting at ‘1’s, it checks for larger squares by adding each new level found to a queue for further exploration. BFS can be easier to handle for large data sets as it does not rely on recursion, thus avoiding stack overflow issues. However, it requires additional memory for the queue.

Here’s an example:

from collections import deque

def bfs(matrix, row, col, rows, cols):
    queue = deque([(row, col, 1)])
    total = 0
    while queue:
        r, c, size = queue.popleft()
        if (r+size > rows) or (c+size > cols):
            continue
        for delta_r in range(size):
            if matrix[r+delta_r][c+size-1] == 0:
                return total
        for delta_c in range(size-1):
            if matrix[r+size-1][c+delta_c] == 0:
                return total
        total += 1
        queue.append((r, c, size+1))
    return total

def countSquares(matrix):
    rows, cols = len(matrix), len(matrix[0])
    count = 0
    for r in range(rows):
        for c in range(cols):
            if matrix[r][c] == 1:
                count += bfs(matrix, r, c, rows, cols)
    return count

matrix = [[1,0,1],[1,1,0],[1,1,0]]
print(countSquares(matrix))

Output: 7

The bfs() function performs a breadth-first search and countSquares() calls this function for each cell of the matrix. It utilizes a queue to manage subsets of submatrices to verify.

Bonus One-Liner Method 5: Functional Programming

This technique uses functional programming with Python’s built-in functions and list comprehension to create a one-liner solution. It is elegant but may be difficult to interpret. This is not always the most efficient, but useful for concise coding or small data sets.

Here’s an example:

countSquares = lambda m: sum(
    min(m[i-1][j], m[i][j-1], m[i-1][j-1]) + 1 if i * j * m[i][j] else m[i][j] 
    for i in range(len(m))
    for j in range(len(m[0]))
    if (m[i][j] := m[i][j])
)

matrix = [[1,0,1],[1,1,0],[1,1,0]]
print(countSquares(matrix))

Output: 7

This one-liner redefines the matrix in-place and calculates the count in a single expression, using lambda and list comprehension.

Summary/Discussion

  • Method 1: Dynamic Programming. This method offers optimal performance with reduced time complexity. However, it requires a good understanding of dynamic programming concepts for implementation.
  • Method 2: Brute Force with Optimization. This solution is straightforward but not as efficient, especially as the size of the matrix increases, due to the nested iterations.
  • Method 3: Depth-First Search. DFS provides an algorithmic and recursive way to solve the problem, but may lead to performance issues with very large matrices.
  • Method 4: Breadth-First Search. BFS avoids the recursive pitfalls of DFS, but uses more memory due to the need for maintaining a queue.
  • Bonus One-Liner Method 5: Functional Programming. This method is elegant and quick to write but less transparent and can be less efficient due to its condensed nature.