840. Magic Squares In Grid
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.
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:
- Valid numbers: All values must be between 1 and 9
- Distinctness: All 9 numbers must be different (which means we need exactly the numbers 1-9)
- 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:
- Iterate through each position
(i, j)
in the grid that could serve as the top-left corner of a 3×3 subgrid - 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 - Sum up all the valid magic squares found
Implementation Details of the check
function:
-
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
-
Data Structures Used:
- A
set s
to track distinct numbers and ensure we have exactly 9 different values - Arrays
row[3]
andcol[3]
to store the sum of each row and column - Variables
a
andb
to store the sums of the main diagonal and anti-diagonal
- A
-
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 suma
- If on the anti-diagonal (
x - i == 2 - (y - j)
), add to diagonal sumb
- Check if the value
-
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
- Check if we have exactly 9 distinct numbers:
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 EvaluatorExample 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
andcol
, 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
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!