Facebook Pixel

840. Magic Squares In Grid

MediumArrayHash TableMathMatrix
Leetcode Link

Problem Description

A magic square is a 3×3 grid where all numbers from 1 to 9 appear exactly once, and every row, column, and both diagonals sum to the same value (which is always 15 for a 3×3 magic square with numbers 1-9).

Given a row × col grid of integers, you need to count how many 3×3 subgrids within it form valid magic squares.

Key requirements for a valid magic square subgrid:

  • Must be exactly 3×3 in size
  • Must contain only the numbers 1 through 9
  • Each number from 1 to 9 must appear exactly once (no duplicates)
  • All three rows must have the same sum
  • All three columns must have the same sum
  • Both diagonals must have the same sum
  • All these sums must be equal to each other

Important note: While a valid magic square can only contain numbers 1-9, the input grid may contain numbers up to 15. Any 3×3 subgrid containing numbers outside the range 1-9 cannot be a magic square.

The task is to examine all possible 3×3 subgrids in the given grid and return the total count of those that are valid magic squares.

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

Intuition

Since we need to find all valid magic square subgrids, the most straightforward approach is to check every possible 3×3 subgrid in the given matrix. We can't optimize much here because we need to verify multiple conditions for each potential magic square.

For each 3×3 subgrid, we need to verify several properties:

  1. Valid numbers: All values must be between 1 and 9
  2. Distinctness: All 9 numbers must be different (which means we need exactly the numbers 1-9)
  3. Equal sums: All rows, columns, and diagonals must sum to the same value

The key insight is that if we have the numbers 1 through 9 appearing exactly once, the sum of each row, column, and diagonal in a valid magic square must be 15 (since 1+2+...+9 = 45, and 45/3 = 15 for each row/column).

To check each subgrid efficiently, we can:

  • Use a set to track if we have exactly 9 distinct numbers
  • Calculate the sum for each row, column, and both diagonals as we iterate through the 3×3 subgrid
  • Compare all these sums to ensure they're equal

The enumeration approach works well here because:

  • The subgrid size is fixed and small (3×3)
  • We need to examine every element in each potential subgrid anyway
  • There's no pattern we can exploit to skip checking certain subgrids

By systematically checking each possible top-left corner position (i, j) and validating the 3×3 subgrid starting from that position, we ensure we don't miss any valid magic squares.

Learn more about Math patterns.

Solution Approach

The solution uses an enumeration approach where we check every possible 3×3 subgrid in the matrix.

Main Algorithm Structure:

  1. Iterate through each position (i, j) in the grid that could serve as the top-left corner of a 3×3 subgrid
  2. For each position, call a helper function check(i, j) to verify if the 3×3 subgrid starting at that position is a magic square
  3. Sum up all the valid magic squares found

Implementation Details of the check function:

  1. Boundary Check: First verify that a 3×3 subgrid starting at (i, j) doesn't go out of bounds:

    if i + 3 > m or j + 3 > n:
        return 0
  2. Data Structures Used:

    • A set s to track distinct numbers and ensure we have exactly 9 different values
    • Arrays row[3] and col[3] to store the sum of each row and column
    • Variables a and b to store the sums of the main diagonal and anti-diagonal
  3. Single Pass Validation: Iterate through the 3×3 subgrid once, and for each element at position (x, y):

    • Check if the value v is within the valid range [1, 9]. If not, return 0
    • Add the value to the set s
    • Update the corresponding row sum: row[x - i] += v
    • Update the corresponding column sum: col[y - j] += v
    • If on the main diagonal (x - i == y - j), add to diagonal sum a
    • If on the anti-diagonal (x - i == 2 - (y - j)), add to diagonal sum b
  4. Final Validation:

    • Check if we have exactly 9 distinct numbers: len(s) != 9
    • Check if both diagonals have the same sum: a != b
    • Check if all rows equal the diagonal sum: any(x != a for x in row)
    • Check if all columns equal the diagonal sum: any(x != a for x in col)
    • If all conditions are met, return 1; otherwise, return 0

Time Complexity: O(m × n) where m and n are the dimensions of the grid. For each possible top-left corner, we perform a constant amount of work (checking a 3×3 subgrid is O(9) = O(1)).

Space Complexity: O(1) as we only use a fixed amount of extra space regardless of input size (the set can hold at most 9 elements, and the arrays are of fixed size 3).

Ready to land your dream job?

Unlock your dream job with a 3-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 4×5 grid:

grid = [[4, 3, 8, 4, 5],
        [9, 5, 1, 9, 8],
        [2, 7, 6, 2, 3],
        [6, 1, 3, 7, 2]]

We need to check all possible 3×3 subgrids. Since our grid is 4×5, we can have top-left corners at positions where i ≤ 1 and j ≤ 2 (to ensure the 3×3 subgrid fits).

Step 1: Check subgrid starting at (0, 0)

The 3×3 subgrid is:

4 3 8
9 5 1
2 7 6

Let's validate this subgrid:

  • Range check: All values are in [1, 9] ✓
  • Distinct values: {4, 3, 8, 9, 5, 1, 2, 7, 6} - we have 9 distinct values ✓
  • Row sums: Row 0: 4+3+8=15, Row 1: 9+5+1=15, Row 2: 2+7+6=15 ✓
  • Column sums: Col 0: 4+9+2=15, Col 1: 3+5+7=15, Col 2: 8+1+6=15 ✓
  • Diagonal sums: Main: 4+5+6=15, Anti: 8+5+2=15 ✓

All sums equal 15, so this is a valid magic square! Count = 1.

Step 2: Check subgrid starting at (0, 1)

The 3×3 subgrid is:

3 8 4
5 1 9
7 6 2

Let's validate:

  • Range check: All values are in [1, 9] ✓
  • Distinct values: {3, 8, 4, 5, 1, 9, 7, 6, 2} - we have 9 distinct values ✓
  • Row sums: Row 0: 3+8+4=15, Row 1: 5+1+9=15, Row 2: 7+6+2=15 ✓
  • Column sums: Col 0: 3+5+7=15, Col 1: 8+1+6=15, Col 2: 4+9+2=15 ✓
  • Diagonal sums: Main: 3+1+2=6, Anti: 4+1+7=12 ✗

The diagonal sums don't match, so this is NOT a magic square.

Step 3: Check subgrid starting at (0, 2)

The 3×3 subgrid is:

8 4 5
1 9 8
6 2 3

Let's validate:

  • Distinct values: {8, 4, 5, 1, 9, 8, 6, 2, 3} - we have duplicate 8s ✗

Since we don't have exactly 9 distinct values (8 appears twice), this cannot be a magic square.

Step 4: Check subgrid starting at (1, 0)

The 3×3 subgrid is:

9 5 1
2 7 6
6 1 3

Let's validate:

  • Distinct values: {9, 5, 1, 2, 7, 6, 6, 1, 3} - we have duplicate 1s and 6s ✗

This cannot be a magic square due to duplicates.

Step 5: Check subgrid starting at (1, 1)

The 3×3 subgrid is:

5 1 9
7 6 2
1 3 7

Let's validate:

  • Distinct values: {5, 1, 9, 7, 6, 2, 1, 3, 7} - we have duplicate 1s and 7s ✗

This cannot be a magic square due to duplicates.

Step 6: Check subgrid starting at (1, 2)

The 3×3 subgrid is:

1 9 8
6 2 3
3 7 2

Let's validate:

  • Distinct values: {1, 9, 8, 6, 2, 3, 3, 7, 2} - we have duplicate 2s and 3s ✗

This cannot be a magic square due to duplicates.

Final Result: Out of 6 possible 3×3 subgrids, only 1 is a valid magic square.

The algorithm efficiently checks each condition in a single pass through each 3×3 subgrid, accumulating row sums, column sums, and diagonal sums while simultaneously checking for distinct values and valid range.

Solution Implementation

1class Solution:
2    def numMagicSquaresInside(self, grid: List[List[int]]) -> int:
3        def is_magic_square(row: int, col: int) -> int:
4            """
5            Check if a 3x3 subgrid starting at (row, col) is a magic square.
6            A magic square contains all numbers 1-9 exactly once and has equal sums
7            for all rows, columns, and diagonals.
8          
9            Args:
10                row: Starting row index of the 3x3 subgrid
11                col: Starting column index of the 3x3 subgrid
12              
13            Returns:
14                1 if it's a magic square, 0 otherwise
15            """
16            # Check if 3x3 subgrid is within bounds
17            if row + 3 > rows or col + 3 > cols:
18                return 0
19          
20            # Track unique numbers and sums
21            unique_numbers = set()
22            row_sums = [0] * 3
23            col_sums = [0] * 3
24            main_diagonal_sum = 0
25            anti_diagonal_sum = 0
26          
27            # Iterate through the 3x3 subgrid
28            for i in range(row, row + 3):
29                for j in range(col, col + 3):
30                    value = grid[i][j]
31                  
32                    # Check if value is in valid range [1, 9]
33                    if value < 1 or value > 9:
34                        return 0
35                  
36                    # Add to set of unique numbers
37                    unique_numbers.add(value)
38                  
39                    # Calculate relative position within 3x3 subgrid
40                    relative_row = i - row
41                    relative_col = j - col
42                  
43                    # Update row and column sums
44                    row_sums[relative_row] += value
45                    col_sums[relative_col] += value
46                  
47                    # Update main diagonal sum (top-left to bottom-right)
48                    if relative_row == relative_col:
49                        main_diagonal_sum += value
50                  
51                    # Update anti-diagonal sum (top-right to bottom-left)
52                    if relative_row == 2 - relative_col:
53                        anti_diagonal_sum += value
54          
55            # Check if all numbers 1-9 are present exactly once
56            if len(unique_numbers) != 9:
57                return 0
58          
59            # Check if both diagonals have the same sum
60            if main_diagonal_sum != anti_diagonal_sum:
61                return 0
62          
63            # Check if all rows have the same sum as the main diagonal
64            if any(row_sum != main_diagonal_sum for row_sum in row_sums):
65                return 0
66          
67            # Check if all columns have the same sum as the main diagonal
68            if any(col_sum != main_diagonal_sum for col_sum in col_sums):
69                return 0
70          
71            return 1
72      
73        # Get grid dimensions
74        rows, cols = len(grid), len(grid[0])
75      
76        # Count all magic squares by checking each possible 3x3 subgrid
77        return sum(is_magic_square(i, j) for i in range(rows) for j in range(cols))
78
1class Solution {
2    // Grid dimensions
3    private int rows;
4    private int cols;
5    private int[][] grid;
6
7    /**
8     * Counts the number of 3x3 magic squares in the grid.
9     * A magic square is a 3x3 grid containing distinct numbers from 1 to 9
10     * where all rows, columns, and diagonals sum to the same value (15).
11     * 
12     * @param grid The input 2D grid
13     * @return The count of valid 3x3 magic squares
14     */
15    public int numMagicSquaresInside(int[][] grid) {
16        this.rows = grid.length;
17        this.cols = grid[0].length;
18        this.grid = grid;
19      
20        int count = 0;
21      
22        // Check every possible 3x3 subgrid starting position
23        for (int row = 0; row < rows; ++row) {
24            for (int col = 0; col < cols; ++col) {
25                count += checkMagicSquare(row, col);
26            }
27        }
28      
29        return count;
30    }
31
32    /**
33     * Checks if a 3x3 subgrid starting at position (startRow, startCol) is a magic square.
34     * 
35     * @param startRow The starting row index of the 3x3 subgrid
36     * @param startCol The starting column index of the 3x3 subgrid
37     * @return 1 if the subgrid is a magic square, 0 otherwise
38     */
39    private int checkMagicSquare(int startRow, int startCol) {
40        // Check if 3x3 subgrid fits within bounds
41        if (startRow + 3 > rows || startCol + 3 > cols) {
42            return 0;
43        }
44      
45        // Track frequency of numbers (index 1-9 used, others ignored)
46        int[] frequency = new int[16];
47      
48        // Store sum of each row and column
49        int[] rowSums = new int[3];
50        int[] colSums = new int[3];
51      
52        // Store diagonal sums
53        int mainDiagonalSum = 0;  // Top-left to bottom-right
54        int antiDiagonalSum = 0;  // Top-right to bottom-left
55      
56        // Process each cell in the 3x3 subgrid
57        for (int row = startRow; row < startRow + 3; ++row) {
58            for (int col = startCol; col < startCol + 3; ++col) {
59                int value = grid[row][col];
60              
61                // Validate: number must be 1-9 and appear exactly once
62                if (value < 1 || value > 9 || ++frequency[value] > 1) {
63                    return 0;
64                }
65              
66                // Calculate relative position within 3x3 subgrid
67                int relativeRow = row - startRow;
68                int relativeCol = col - startCol;
69              
70                // Add to row and column sums
71                rowSums[relativeRow] += value;
72                colSums[relativeCol] += value;
73              
74                // Check if on main diagonal (top-left to bottom-right)
75                if (relativeRow == relativeCol) {
76                    mainDiagonalSum += value;
77                }
78              
79                // Check if on anti-diagonal (top-right to bottom-left)
80                if (relativeRow + relativeCol == 2) {
81                    antiDiagonalSum += value;
82                }
83            }
84        }
85      
86        // Both diagonals must have equal sums
87        if (mainDiagonalSum != antiDiagonalSum) {
88            return 0;
89        }
90      
91        // All rows and columns must have the same sum as the diagonals
92        for (int i = 0; i < 3; ++i) {
93            if (rowSums[i] != mainDiagonalSum || colSums[i] != mainDiagonalSum) {
94                return 0;
95            }
96        }
97      
98        // All conditions satisfied - this is a magic square
99        return 1;
100    }
101}
102
1class Solution {
2public:
3    int numMagicSquaresInside(vector<vector<int>>& grid) {
4        int rows = grid.size();
5        int cols = grid[0].size();
6        int count = 0;
7      
8        // Lambda function to check if a 3x3 subgrid starting at (row, col) is a magic square
9        auto isMagicSquare = [&](int row, int col) -> int {
10            // Check if 3x3 subgrid is within bounds
11            if (row + 3 > rows || col + 3 > cols) {
12                return 0;
13            }
14          
15            // Track frequency of numbers (index 0-15 for potential values)
16            vector<int> frequency(16, 0);
17            // Store sum of each row
18            vector<int> rowSums(3, 0);
19            // Store sum of each column
20            vector<int> colSums(3, 0);
21            // Sum of main diagonal (top-left to bottom-right)
22            int mainDiagonalSum = 0;
23            // Sum of anti-diagonal (top-right to bottom-left)
24            int antiDiagonalSum = 0;
25          
26            // Iterate through the 3x3 subgrid
27            for (int i = row; i < row + 3; ++i) {
28                for (int j = col; j < col + 3; ++j) {
29                    int value = grid[i][j];
30                  
31                    // Check if value is between 1 and 9, and appears only once
32                    if (value < 1 || value > 9 || ++frequency[value] > 1) {
33                        return 0;
34                    }
35                  
36                    // Add value to corresponding row sum
37                    rowSums[i - row] += value;
38                    // Add value to corresponding column sum
39                    colSums[j - col] += value;
40                  
41                    // Check if on main diagonal
42                    if (i - row == j - col) {
43                        mainDiagonalSum += value;
44                    }
45                  
46                    // Check if on anti-diagonal
47                    if (i - row + j - col == 2) {
48                        antiDiagonalSum += value;
49                    }
50                }
51            }
52          
53            // Check if both diagonals have the same sum
54            if (mainDiagonalSum != antiDiagonalSum) {
55                return 0;
56            }
57          
58            // Check if all rows and columns have the same sum as the diagonals
59            for (int k = 0; k < 3; ++k) {
60                if (rowSums[k] != mainDiagonalSum || colSums[k] != mainDiagonalSum) {
61                    return 0;
62                }
63            }
64          
65            // Valid magic square found
66            return 1;
67        };
68      
69        // Check every possible 3x3 subgrid in the grid
70        for (int i = 0; i < rows; ++i) {
71            for (int j = 0; j < cols; ++j) {
72                count += isMagicSquare(i, j);
73            }
74        }
75      
76        return count;
77    }
78};
79
1/**
2 * Counts the number of 3x3 magic squares in a grid
3 * A magic square is a 3x3 grid containing distinct numbers from 1-9
4 * where all rows, columns, and diagonals sum to the same value (15)
5 */
6function numMagicSquaresInside(grid: number[][]): number {
7    const rows: number = grid.length;
8    const cols: number = grid[0].length;
9  
10    /**
11     * Checks if a 3x3 subgrid starting at position (startRow, startCol) is a magic square
12     * @param startRow - Starting row index of the 3x3 subgrid
13     * @param startCol - Starting column index of the 3x3 subgrid
14     * @returns 1 if the subgrid is a magic square, 0 otherwise
15     */
16    const checkMagicSquare = (startRow: number, startCol: number): number => {
17        // Check if 3x3 subgrid fits within grid boundaries
18        if (startRow + 3 > rows || startCol + 3 > cols) {
19            return 0;
20        }
21      
22        // Track occurrences of each number (index 1-9 used, 0 and 10-15 unused)
23        const numberCount: number[] = Array(16).fill(0);
24      
25        // Track sum of each row and column in the 3x3 subgrid
26        const rowSums: number[] = Array(3).fill(0);
27        const colSums: number[] = Array(3).fill(0);
28      
29        // Track diagonal sums: mainDiagonal (top-left to bottom-right), antiDiagonal (top-right to bottom-left)
30        let mainDiagonalSum: number = 0;
31        let antiDiagonalSum: number = 0;
32      
33        // Iterate through the 3x3 subgrid
34        for (let row = startRow; row < startRow + 3; row++) {
35            for (let col = startCol; col < startCol + 3; col++) {
36                const currentValue: number = grid[row][col];
37              
38                // Validate: number must be 1-9 and appear exactly once
39                if (currentValue < 1 || currentValue > 9 || ++numberCount[currentValue] > 1) {
40                    return 0;
41                }
42              
43                // Calculate relative position within 3x3 subgrid
44                const relativeRow: number = row - startRow;
45                const relativeCol: number = col - startCol;
46              
47                // Add to row and column sums
48                rowSums[relativeRow] += currentValue;
49                colSums[relativeCol] += currentValue;
50              
51                // Add to main diagonal sum if on main diagonal
52                if (relativeRow === relativeCol) {
53                    mainDiagonalSum += currentValue;
54                }
55              
56                // Add to anti-diagonal sum if on anti-diagonal
57                if (relativeRow === 2 - relativeCol) {
58                    antiDiagonalSum += currentValue;
59                }
60            }
61        }
62      
63        // Check if both diagonals have the same sum
64        if (mainDiagonalSum !== antiDiagonalSum) {
65            return 0;
66        }
67      
68        // Check if all rows and columns have the same sum as the diagonals
69        for (let index = 0; index < 3; index++) {
70            if (rowSums[index] !== mainDiagonalSum || colSums[index] !== mainDiagonalSum) {
71                return 0;
72            }
73        }
74      
75        // All conditions met - this is a magic square
76        return 1;
77    };
78  
79    // Count total magic squares by checking each possible 3x3 position
80    let totalMagicSquares: number = 0;
81    for (let row = 0; row < rows; row++) {
82        for (let col = 0; col < cols; col++) {
83            totalMagicSquares += checkMagicSquare(row, col);
84        }
85    }
86  
87    return totalMagicSquares;
88}
89

Time and Space Complexity

Time Complexity: O(m * n)

For each position (i, j) in the grid, we call the check function which examines a 3×3 subgrid. The check function performs:

  • A nested loop iterating through 9 cells (3×3), which takes O(9) = O(1) time
  • Set operations (add) which are O(1) on average
  • Checking conditions for rows, columns, and diagonals, which involves iterating through arrays of size 3, taking O(3) = O(1) time

Since we call check for each of the m * n positions in the grid, and each call takes O(1) time, the overall time complexity is O(m * n).

Space Complexity: O(1)

The space used by the algorithm includes:

  • A set s that stores at most 9 unique values: O(9) = O(1)
  • Two arrays row and col, each of size 3: O(3) + O(3) = O(6) = O(1)
  • A few integer variables (a, b, m, n, etc.): O(1)

The space usage doesn't depend on the input size, so the overall space complexity is O(1).

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

Common Pitfalls

1. Incorrect Anti-Diagonal Calculation

One of the most common mistakes is incorrectly identifying which cells belong to the anti-diagonal (top-right to bottom-left).

Incorrect approach:

# Wrong: This checks for bottom-left to top-right
if relative_row + relative_col == 2:
    anti_diagonal_sum += value

Correct approach:

# Correct: For anti-diagonal in a 3x3 grid
if relative_row == 2 - relative_col:
    anti_diagonal_sum += value

The anti-diagonal positions in a 3x3 grid are: (0,2), (1,1), (2,0), which satisfy the equation row + col = 2.

2. Forgetting to Check Value Range

Many solutions forget that while a magic square must contain numbers 1-9, the input grid can contain numbers up to 15.

Pitfall example:

# Missing range check - will incorrectly validate subgrids with numbers > 9
unique_numbers.add(value)
# Continue processing without checking if 1 <= value <= 9

Solution:

# Always check the valid range first
if value < 1 or value > 9:
    return 0
unique_numbers.add(value)

3. Off-by-One Errors in Boundary Checks

A subtle but critical error is using incorrect boundary conditions when checking if a 3x3 subgrid fits within the grid.

Incorrect:

# Wrong: This allows starting positions that would go out of bounds
if row + 2 < rows and col + 2 < cols:
    # Process subgrid

Correct:

# Correct: Ensures the entire 3x3 subgrid fits
if row + 3 > rows or col + 3 > cols:
    return 0

4. Not Verifying All Numbers 1-9 Are Present

Some solutions check for duplicates but forget to ensure all numbers from 1 to 9 are actually present.

Incomplete validation:

# This only checks for 9 unique numbers, not that they're specifically 1-9
if len(unique_numbers) == 9:
    # Assumes it's valid

Complete validation:

# First check range for each value (1-9)
if value < 1 or value > 9:
    return 0
# Then check we have exactly 9 unique numbers
if len(unique_numbers) != 9:
    return 0
# This ensures we have exactly the numbers 1-9 with no duplicates

5. Assuming Magic Constant is Always 15

While it's true that a 3x3 magic square with numbers 1-9 always sums to 15, hardcoding this value can lead to errors if you don't validate all constraints properly.

Risky approach:

# Hardcoding the magic constant
MAGIC_SUM = 15
if row_sum != MAGIC_SUM:
    return 0

Robust approach:

# Calculate the actual sum from the diagonals and use that as reference
if main_diagonal_sum != anti_diagonal_sum:
    return 0
if any(row_sum != main_diagonal_sum for row_sum in row_sums):
    return 0
# This ensures all sums are equal without assuming a specific value
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More