Program to check how many ways we can choose empty cells of a matrix in python

Suppose we have an N x N binary matrix where 0 represents empty cells and 1 represents blocked cells. We need to find the number of ways to choose N empty cells such that every row and every column has at least one chosen cell. If the answer is very large, return the result mod 10^9 + 7.

Problem Understanding

Given a matrix like this:

0 0 0 0 0 0 0 1 0

We need to select exactly 3 empty cells (one per row and column). The answer is 4 possible configurations.

Solution Approach

We use backtracking with bit manipulation. For each row, we try placing a cell in each available column and use a bitmask to track which columns are already used.

Algorithm Steps

  • For each row i, try placing a cell in each empty column j
  • Use bitmask to ensure no two cells are in the same column
  • Recursively solve for the next row
  • Base case: when all rows are processed, return 1

Implementation

class Solution:
    def solve(self, matrix):
        n = len(matrix)
        
        def backtrack(row, used_cols):
            # Base case: all rows processed
            if row >= n:
                return 1
            
            ways = 0
            # Try each column in current row
            for col in range(n):
                # Check if cell is empty and column not used
                if matrix[row][col] == 0 and ((1 << col) & used_cols == 0):
                    # Choose this cell and recurse
                    ways += backtrack(row + 1, used_cols | (1 << col))
            
            return ways
        
        return backtrack(0, 0)

# Test with the example
matrix = [
    [0, 0, 0],
    [0, 0, 0],
    [0, 1, 0]
]

solution = Solution()
result = solution.solve(matrix)
print(f"Number of ways: {result}")
Number of ways: 4

How the Algorithm Works

Let's trace through the example:

# Visualize the 4 valid configurations
def print_configurations():
    configurations = [
        [(0,0), (1,1), (2,2)],  # Main diagonal
        [(0,0), (1,2), (2,0)],  # Anti-pattern 1  
        [(0,1), (1,0), (2,2)],  # Anti-pattern 2
        [(0,2), (1,0), (2,0)]   # Column 0 heavy
    ]
    
    for i, config in enumerate(configurations, 1):
        print(f"Configuration {i}: {config}")
        for row in range(3):
            line = ""
            for col in range(3):
                if (row, col) in config:
                    line += "X "
                elif row == 2 and col == 1:  # Blocked cell
                    line += "# "
                else:
                    line += "0 "
            print(line)
        print()

print_configurations()
Configuration 1: [(0, 0), (1, 1), (2, 2)]
X 0 0 
0 X 0 
0 # X 

Configuration 2: [(0, 0), (1, 2), (2, 0)]
X 0 0 
0 0 X 
X # 0 

Configuration 3: [(0, 1), (1, 0), (2, 2)]
0 X 0 
X 0 0 
0 # X 

Configuration 4: [(0, 2), (1, 0), (2, 0)]
0 0 X 
X 0 0 
X # 0 

Time and Space Complexity

Aspect Complexity Explanation
Time O(N!) Each row has at most N choices
Space O(N) Recursion depth

Conclusion

This backtracking solution efficiently counts valid cell selections using bit manipulation to track used columns. The algorithm ensures exactly one cell per row and column while avoiding blocked cells.

Updated on: 2026-03-25T13:09:11+05:30

298 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements