Facebook Pixel

3212. Count Submatrices With Equal Frequency of X and Y

Problem Description

You are given a 2D character matrix grid where each cell grid[i][j] contains one of three possible values: 'X', 'Y', or '.'.

Your task is to count how many submatrices satisfy ALL of the following conditions:

  1. The submatrix must include the top-left corner of the grid (position grid[0][0])
  2. The submatrix must contain an equal number of 'X' and 'Y' characters
  3. The submatrix must contain at least one 'X' character

A submatrix is defined by its top-left corner at (0, 0) and any bottom-right corner at position (i, j) where 0 ≤ i < m and 0 ≤ j < n (where m and n are the dimensions of the grid).

For example, if you have a 3×3 grid, possible submatrices that start at (0, 0) could end at positions like (0, 0), (0, 1), (1, 1), (2, 2), etc. Each such submatrix is checked to see if it meets the three conditions above.

The dot character '.' is neutral and doesn't count as either 'X' or 'Y'.

Return the total number of submatrices that satisfy all the conditions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Since we need to check all submatrices that start at position (0, 0), we essentially need to examine every possible bottom-right corner position (i, j) in the grid. For each such position, we need to quickly determine:

  • How many 'X' characters are in the submatrix from (0, 0) to (i, j)
  • How many 'Y' characters are in the submatrix from (0, 0) to (i, j)

If we calculate this naively for each position, we'd have to traverse the entire submatrix every time, which would be inefficient.

The key insight is to use 2D prefix sums. This technique allows us to precompute cumulative counts as we traverse the grid, so we can instantly know the count of 'X' and 'Y' characters in any submatrix starting from (0, 0).

For each position (i, j), we can calculate the prefix sum using the formula:

  • s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + current_cell_value

The subtraction of s[i-1][j-1] is necessary because when we add s[i-1][j] and s[i][j-1], we're counting the rectangle from (0, 0) to (i-1, j-1) twice.

We maintain two separate prefix sums: one for counting 'X' characters and another for counting 'Y' characters. As we build these prefix sums for each position, we can immediately check if:

  1. The count of 'X' is greater than 0 (at least one 'X' exists)
  2. The count of 'X' equals the count of 'Y' (equal frequency)

If both conditions are met, we've found a valid submatrix and increment our answer.

This approach is efficient because we only traverse the grid once, and at each position, we can determine in constant time whether the submatrix from (0, 0) to that position is valid.

Learn more about Prefix Sum patterns.

Solution Approach

We implement the solution using a 2D prefix sum approach. Here's how the algorithm works:

Data Structure Setup: We create a 3D array s with dimensions (m+1) × (n+1) × 2, where:

  • s[i][j][0] stores the count of 'X' characters in the submatrix from (0, 0) to (i-1, j-1)
  • s[i][j][1] stores the count of 'Y' characters in the submatrix from (0, 0) to (i-1, j-1)
  • We use 1-indexed arrays (hence m+1 and n+1) to avoid boundary checks

Algorithm Steps:

  1. Initialize the prefix sum array with zeros and set answer counter ans = 0

  2. Traverse the grid row by row, and for each cell at position (i, j):

    a. Calculate prefix sums using the inclusion-exclusion principle:

    s[i][j][0] = s[i-1][j][0] + s[i][j-1][0] - s[i-1][j-1][0]
    s[i][j][1] = s[i-1][j][1] + s[i][j-1][1] - s[i-1][j-1][1]

    b. Update counts based on the current cell:

    • If the cell contains 'X' or 'Y', increment the corresponding counter
    • The clever part: ord(x) & 1 gives us 0 for 'X' (ASCII 88) and 1 for 'Y' (ASCII 89)
    • If the cell is '.', we skip updating (it doesn't contribute to either count)

    c. Check validity of the submatrix from (0, 0) to current position:

    • If s[i][j][0] > 0 (at least one 'X' exists) AND
    • If s[i][j][0] == s[i][j][1] (equal frequency of 'X' and 'Y')
    • Then increment the answer counter
  3. Return the final count of valid submatrices

Time Complexity: O(m × n) where m and n are the dimensions of the grid, as we visit each cell exactly once.

Space Complexity: O(m × n) for storing the prefix sum array.

The beauty of this solution is that it computes everything in a single pass through the grid, making it highly efficient compared to checking every possible submatrix individually.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Consider this 3×3 grid:

X Y .
Y X Y
. Y X

We'll build our prefix sum array s with dimensions 4×4×2 (using 1-indexing). Initially, all values are 0.

Step-by-step traversal:

Position (0,0) - Character 'X':

  • Calculate prefix sums: s[1][1][0] = 0 + 0 - 0 = 0, s[1][1][1] = 0 + 0 - 0 = 0
  • Current cell is 'X', so increment s[1][1][0] by 1 → s[1][1][0] = 1
  • Check validity: s[1][1][0] = 1 > 0 ✓ and s[1][1][0] = 1 ≠ s[1][1][1] = 0
  • No valid submatrix yet

Position (0,1) - Character 'Y':

  • Calculate prefix sums: s[1][2][0] = 0 + 1 - 0 = 1, s[1][2][1] = 0 + 0 - 0 = 0
  • Current cell is 'Y', so increment s[1][2][1] by 1 → s[1][2][1] = 1
  • Check validity: s[1][2][0] = 1 > 0 ✓ and s[1][2][0] = 1 == s[1][2][1] = 1
  • Found valid submatrix! ans = 1

Position (0,2) - Character '.':

  • Calculate prefix sums: s[1][3][0] = 0 + 1 - 0 = 1, s[1][3][1] = 0 + 1 - 0 = 1
  • Current cell is '.', so no increment
  • Check validity: s[1][3][0] = 1 > 0 ✓ and s[1][3][0] = 1 == s[1][3][1] = 1
  • Found valid submatrix! ans = 2

Position (1,0) - Character 'Y':

  • Calculate prefix sums: s[2][1][0] = 1 + 0 - 0 = 1, s[2][1][1] = 0 + 0 - 0 = 0
  • Current cell is 'Y', so increment s[2][1][1] by 1 → s[2][1][1] = 1
  • Check validity: s[2][1][0] = 1 > 0 ✓ and s[2][1][0] = 1 == s[2][1][1] = 1
  • Found valid submatrix! ans = 3

Position (1,1) - Character 'X':

  • Calculate prefix sums: s[2][2][0] = 1 + 1 - 0 = 2, s[2][2][1] = 1 + 1 - 0 = 2
  • Current cell is 'X', so increment s[2][2][0] by 1 → s[2][2][0] = 3
  • Check validity: s[2][2][0] = 3 > 0 ✓ and s[2][2][0] = 3 ≠ s[2][2][1] = 2
  • No valid submatrix

Position (1,2) - Character 'Y':

  • Calculate prefix sums: s[2][3][0] = 1 + 3 - 1 = 3, s[2][3][1] = 1 + 2 - 1 = 2
  • Current cell is 'Y', so increment s[2][3][1] by 1 → s[2][3][1] = 3
  • Check validity: s[2][3][0] = 3 > 0 ✓ and s[2][3][0] = 3 == s[2][3][1] = 3
  • Found valid submatrix! ans = 4

Continuing this process for the remaining positions (2,0), (2,1), and (2,2), we find that:

  • Position (2,0): Not valid (3 X's, 2 Y's)
  • Position (2,1): Not valid (4 X's, 3 Y's)
  • Position (2,2): Valid (4 X's, 4 Y's) → ans = 5

Final answer: 5 valid submatrices

The valid submatrices are those ending at positions: (0,1), (0,2), (1,0), (1,2), and (2,2).

Solution Implementation

1class Solution:
2    def numberOfSubmatrices(self, grid: List[List[str]]) -> int:
3        # Get grid dimensions
4        rows, cols = len(grid), len(grid[0])
5      
6        # Initialize prefix sum array with padding
7        # prefix_sum[i][j][0] stores count of first type characters (e.g., 'Y')
8        # prefix_sum[i][j][1] stores count of second type characters (e.g., 'X')
9        prefix_sum = [[[0] * 2 for _ in range(cols + 1)] for _ in range(rows + 1)]
10      
11        result_count = 0
12      
13        # Iterate through each cell of the grid (1-indexed for easier prefix sum calculation)
14        for row_idx, row_data in enumerate(grid, 1):
15            for col_idx, cell_value in enumerate(row_data, 1):
16                # Calculate prefix sum for both character types using inclusion-exclusion principle
17                # Copy counts from top and left cells, subtract overlap
18                prefix_sum[row_idx][col_idx][0] = (prefix_sum[row_idx - 1][col_idx][0] + 
19                                                   prefix_sum[row_idx][col_idx - 1][0] - 
20                                                   prefix_sum[row_idx - 1][col_idx - 1][0])
21              
22                prefix_sum[row_idx][col_idx][1] = (prefix_sum[row_idx - 1][col_idx][1] + 
23                                                   prefix_sum[row_idx][col_idx - 1][1] - 
24                                                   prefix_sum[row_idx - 1][col_idx - 1][1])
25              
26                # If current cell is not empty, increment the appropriate counter
27                if cell_value != ".":
28                    # Use ASCII value's least significant bit to determine character type
29                    # 'X' (ASCII 88) & 1 = 0, 'Y' (ASCII 89) & 1 = 1
30                    character_type = ord(cell_value) & 1
31                    prefix_sum[row_idx][col_idx][character_type] += 1
32              
33                # Check if submatrix from (0,0) to current position is valid
34                # Valid means: has at least one 'X' and equal counts of both types
35                if (prefix_sum[row_idx][col_idx][0] > 0 and 
36                    prefix_sum[row_idx][col_idx][0] == prefix_sum[row_idx][col_idx][1]):
37                    result_count += 1
38      
39        return result_count
40
1class Solution {
2    public int numberOfSubmatrices(char[][] grid) {
3        int rows = grid.length;
4        int cols = grid[0].length;
5      
6        // Create a 3D prefix sum array
7        // prefixSum[i][j][0] stores count of 'X' from (0,0) to (i-1,j-1)
8        // prefixSum[i][j][1] stores count of 'Y' from (0,0) to (i-1,j-1)
9        int[][][] prefixSum = new int[rows + 1][cols + 1][2];
10      
11        int result = 0;
12      
13        // Build prefix sum array and count valid submatrices
14        for (int i = 1; i <= rows; i++) {
15            for (int j = 1; j <= cols; j++) {
16                // Calculate prefix sum for 'X' count using inclusion-exclusion principle
17                prefixSum[i][j][0] = prefixSum[i - 1][j][0] 
18                                    + prefixSum[i][j - 1][0] 
19                                    - prefixSum[i - 1][j - 1][0]
20                                    + (grid[i - 1][j - 1] == 'X' ? 1 : 0);
21              
22                // Calculate prefix sum for 'Y' count using inclusion-exclusion principle
23                prefixSum[i][j][1] = prefixSum[i - 1][j][1] 
24                                    + prefixSum[i][j - 1][1] 
25                                    - prefixSum[i - 1][j - 1][1]
26                                    + (grid[i - 1][j - 1] == 'Y' ? 1 : 0);
27              
28                // Check if submatrix from (0,0) to (i-1,j-1) is valid
29                // Valid means: contains at least one 'X' and equal number of 'X' and 'Y'
30                if (prefixSum[i][j][0] > 0 && prefixSum[i][j][0] == prefixSum[i][j][1]) {
31                    result++;
32                }
33            }
34        }
35      
36        return result;
37    }
38}
39
1class Solution {
2public:
3    int numberOfSubmatrices(vector<vector<char>>& grid) {
4        int rows = grid.size();
5        int cols = grid[0].size();
6      
7        // Create prefix sum array with extra padding for easier calculation
8        // prefixSum[i][j][0] stores count of 'X' from (0,0) to (i-1,j-1)
9        // prefixSum[i][j][1] stores count of 'Y' from (0,0) to (i-1,j-1)
10        vector<vector<vector<int>>> prefixSum(rows + 1, 
11                                              vector<vector<int>>(cols + 1, 
12                                              vector<int>(2, 0)));
13      
14        int result = 0;
15      
16        // Build prefix sum array and count valid submatrices
17        for (int i = 1; i <= rows; ++i) {
18            for (int j = 1; j <= cols; ++j) {
19                // Calculate prefix sum for 'X' count using inclusion-exclusion principle
20                prefixSum[i][j][0] = prefixSum[i - 1][j][0] 
21                                    + prefixSum[i][j - 1][0] 
22                                    - prefixSum[i - 1][j - 1][0]
23                                    + (grid[i - 1][j - 1] == 'X' ? 1 : 0);
24              
25                // Calculate prefix sum for 'Y' count using inclusion-exclusion principle
26                prefixSum[i][j][1] = prefixSum[i - 1][j][1] 
27                                    + prefixSum[i][j - 1][1] 
28                                    - prefixSum[i - 1][j - 1][1]
29                                    + (grid[i - 1][j - 1] == 'Y' ? 1 : 0);
30              
31                // Check if current submatrix from (0,0) to (i-1,j-1) is valid
32                // Valid means: contains at least one 'X' and equal number of 'X' and 'Y'
33                if (prefixSum[i][j][0] > 0 && prefixSum[i][j][0] == prefixSum[i][j][1]) {
34                    ++result;
35                }
36            }
37        }
38      
39        return result;
40    }
41};
42
1/**
2 * Counts the number of submatrices starting from (0,0) that contain equal numbers of 'X' and 'Y',
3 * with at least one 'X' present.
4 * 
5 * @param grid - 2D array of strings containing 'X', 'Y', or other characters
6 * @returns The number of valid submatrices
7 */
8function numberOfSubmatrices(grid: string[][]): number {
9    const rows: number = grid.length;
10    const cols: number = grid[0].length;
11  
12    // Create a 2D prefix sum array where each cell stores [countX, countY]
13    // prefixSum[i][j] represents the count of 'X' and 'Y' in the submatrix from (0,0) to (i-1,j-1)
14    const prefixSum: number[][][] = Array.from(
15        { length: rows + 1 }, 
16        () => Array.from(
17            { length: cols + 1 }, 
18            () => [0, 0]
19        )
20    );
21  
22    let validSubmatricesCount: number = 0;
23
24    // Build prefix sum array and count valid submatrices
25    for (let row = 1; row <= rows; row++) {
26        for (let col = 1; col <= cols; col++) {
27            // Calculate prefix sum for 'X' count using inclusion-exclusion principle
28            prefixSum[row][col][0] = 
29                prefixSum[row - 1][col][0] +           // Add count from top
30                prefixSum[row][col - 1][0] -           // Add count from left
31                prefixSum[row - 1][col - 1][0] +       // Subtract overlapping area
32                (grid[row - 1][col - 1] === 'X' ? 1 : 0); // Add current cell if 'X'
33          
34            // Calculate prefix sum for 'Y' count using inclusion-exclusion principle
35            prefixSum[row][col][1] = 
36                prefixSum[row - 1][col][1] +           // Add count from top
37                prefixSum[row][col - 1][1] -           // Add count from left
38                prefixSum[row - 1][col - 1][1] +       // Subtract overlapping area
39                (grid[row - 1][col - 1] === 'Y' ? 1 : 0); // Add current cell if 'Y'
40          
41            // Check if current submatrix from (0,0) to (row-1,col-1) is valid:
42            // - Must have at least one 'X' (countX > 0)
43            // - Must have equal number of 'X' and 'Y' (countX === countY)
44            if (prefixSum[row][col][0] > 0 && prefixSum[row][col][0] === prefixSum[row][col][1]) {
45                validSubmatricesCount++;
46            }
47        }
48    }
49
50    return validSubmatricesCount;
51}
52

Time and Space Complexity

Time Complexity: O(m × n)

The algorithm uses nested loops to iterate through the grid. The outer loop runs m times (for each row), and the inner loop runs n times (for each column). Within the nested loops, all operations are constant time O(1):

  • Array access and assignment operations for the prefix sum array s
  • Arithmetic operations (addition and subtraction)
  • Character comparison and bitwise operation ord(x) & 1
  • Conditional checks

Therefore, the overall time complexity is O(m × n).

Space Complexity: O(m × n)

The algorithm creates a 3D array s with dimensions (m + 1) × (n + 1) × 2. This array stores the prefix sums for tracking two different types of elements (determined by ord(x) & 1). The total number of elements in this array is (m + 1) × (n + 1) × 2, which is O(m × n) in terms of space complexity.

The remaining variables (m, n, ans, i, j, x) use constant space O(1).

Therefore, the overall space complexity is O(m × n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Character Type Mapping

The code uses ord(cell_value) & 1 to determine character type, which maps 'X' to 0 and 'Y' to 1. However, the problem description and comments suggest checking for 'X' characters specifically (condition: "at least one 'X' character"). This creates confusion about which index represents which character.

The Issue:

  • 'X' (ASCII 88) & 1 = 0, so 'X' counts are stored at index 0
  • 'Y' (ASCII 89) & 1 = 1, so 'Y' counts are stored at index 1
  • But the validity check uses prefix_sum[row_idx][col_idx][0] > 0 to verify "at least one 'X'"

Solution: Make the mapping explicit and consistent:

# Clear mapping
if cell_value == 'X':
    prefix_sum[row_idx][col_idx][0] += 1
elif cell_value == 'Y':
    prefix_sum[row_idx][col_idx][1] += 1

# Then check: at least one 'X' AND equal counts
if (prefix_sum[row_idx][col_idx][0] > 0 and 
    prefix_sum[row_idx][col_idx][0] == prefix_sum[row_idx][col_idx][1]):
    result_count += 1

2. Forgetting the Top-Left Corner Requirement

The current implementation correctly handles this by always computing submatrices from (0,0), but developers might mistakenly try to optimize by checking all possible submatrices, not just those starting at the top-left corner.

Wrong Approach:

# DON'T do this - checks ALL submatrices
for start_row in range(rows):
    for start_col in range(cols):
        for end_row in range(start_row, rows):
            for end_col in range(start_col, cols):
                # Check submatrix...

Correct Approach: Always start from (0,0) as the code currently does.

3. Off-by-One Errors with 1-Indexed Arrays

Using 1-indexed arrays for prefix sums can lead to indexing mistakes when accessing the original grid.

Potential Bug:

# Wrong: using row_idx/col_idx directly on grid
if grid[row_idx][col_idx] != ".":  # IndexError!

Correct Usage:

# Use enumerate with start=1 and access via loop variable
for row_idx, row_data in enumerate(grid, 1):
    for col_idx, cell_value in enumerate(row_data, 1):
        # cell_value is already the correct value from grid

4. Misunderstanding Empty Cells

Developers might incorrectly handle '.' characters by either counting them or excluding submatrices containing them entirely.

Wrong Interpretation:

# DON'T skip submatrices with dots
if has_dot_in_submatrix:
    continue  # Wrong! Dots are neutral

Correct Handling: Dots should be ignored in counting but not prevent a submatrix from being valid. The current code handles this correctly by simply not incrementing any counter when encountering '.'.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More