Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1895. Largest Magic Square - Leetcode Solution

Problem Description

You are given a 2D grid of integers, grid, with m rows and n columns. A magic square is a square subgrid where the sums of every row, every column, and both diagonals are all equal.

Your task is to find the size (side length) of the largest magic square that can be found anywhere in grid. The square must be entirely within the grid, and every element of the square is used exactly once in its corresponding row, column, and diagonal sums.

  • You may assume at least one cell exists in grid.
  • Elements are not reused: each element in a square is counted for only one row, one column, and (if on a diagonal) one diagonal.
  • The side length of the square must be at least 1.

Thought Process

At first glance, it seems we could check every possible square subgrid, and for each, verify if it's a magic square by checking all its rows, columns, and diagonals. However, this brute-force approach would be far too slow for large grids, as the number of possible squares and the checks required for each grow quickly.

To optimize, we want to reduce repeated work. Since checking sums of rows and columns is a common operation, we can precompute prefix sums for rows and columns. This way, we can quickly calculate the sum of any row or column in constant time. For diagonals, since they are less regular, we can also precompute diagonal prefix sums.

The main challenge is efficiently checking, for every possible square of size k (from largest to smallest), whether all its rows, columns, and diagonals sum to the same value. By starting from the largest possible size and stopping at the first valid magic square, we ensure we find the largest one.

Solution Approach

  • Step 1: Precompute Prefix Sums
    • For each row, compute its prefix sum so we can get the sum of any segment in constant time.
    • Similarly, compute prefix sums for columns.
    • For diagonals (top-left to bottom-right and top-right to bottom-left), precompute prefix sums for every diagonal starting from each cell.
  • Step 2: Try All Possible Square Sizes
    • Start from the largest possible square size (min(m, n)), and for each size, check all possible positions where a square of that size fits.
    • For each position, calculate the sum of the first row (as the target sum).
    • Check all rows, columns, and both diagonals in the square. If all sums match the target, record the size.
    • Return the first (largest) size found.
  • Step 3: Efficient Sum Calculation
    • Use precomputed prefix sums to get the sum of any row, column, or diagonal in constant time.
    • This avoids recalculating sums repeatedly for overlapping subgrids.

This approach ensures that we efficiently check all possible magic squares, prioritizing larger sizes and minimizing redundant computation.

Example Walkthrough

Suppose grid = [[7,1,4,5,6],[2,5,1,6,4],[1,5,4,3,2],[1,2,7,3,4]].

  1. The largest possible square is 4x4, but we check all 4x4, 3x3, 2x2, and 1x1 squares.
  2. For a 3x3 square starting at (0,1):
    [[1,4,5],[5,1,6],[5,4,3]]
    • Sum of each row: 1+4+5=10, 5+1+6=12, 5+4+3=12 (not all equal, so not magic)
  3. For a 2x2 square at (1,1):
    [[5,1],[5,4]]
    • Rows: 5+1=6, 5+4=9 (not equal)
  4. For a 2x2 square at (2,2):
    [[4,3],[7,3]]
    • Rows: 4+3=7, 7+3=10 (not equal)
  5. For a 2x2 square at (0,0):
    [[7,1],[2,5]]
    • Rows: 7+1=8, 2+5=7 (not equal)
  6. For a 1x1 square (any cell), all sums are equal by definition. So the minimum answer is 1.
  7. After checking all, let's say the largest magic square found is 3x3 at some position (see code for details).

Time and Space Complexity

  • Brute-force Approach: For each possible square (O(mn * min(m, n)^2)), check all rows, columns, and diagonals (O(k)), leading to O(mn * k^3) time, which is too slow for large grids.
  • Optimized Approach: Precomputing prefix sums for rows, columns, and diagonals takes O(mn) time and space. For each square, checking all rows, columns, and diagonals takes O(k), and there are O(mn) possible top-left corners and O(min(m, n)) possible sizes, giving O(mn * min(m, n) * k) total time. This is much faster and feasible for reasonable grid sizes.
  • Space Complexity: O(mn) for storing prefix sums.

Summary

To find the largest magic square in a grid, we precompute prefix sums for rows, columns, and diagonals to enable fast sum checks. We then examine all possible square sizes and positions, using the prefix sums to verify magic square properties efficiently. This approach is significantly more efficient than brute-force and leverages dynamic programming concepts for optimal performance.

Code Implementation

class Solution:
    def largestMagicSquare(self, grid):
        m, n = len(grid), len(grid[0])
        # Prefix sums for rows and columns
        row_psum = [[0]*(n+1) for _ in range(m)]
        col_psum = [[0]*(n) for _ in range(m+1)]
        for i in range(m):
            for j in range(n):
                row_psum[i][j+1] = row_psum[i][j] + grid[i][j]
                col_psum[i+1][j] = col_psum[i][j] + grid[i][j]
        # Prefix sums for diagonals
        diag1 = [[0]*(n+1) for _ in range(m+1)]  # top-left to bottom-right
        diag2 = [[0]*(n+2) for _ in range(m+1)]  # top-right to bottom-left (1-based for easier indexing)
        for i in range(m):
            for j in range(n):
                diag1[i+1][j+1] = diag1[i][j] + grid[i][j]
                diag2[i+1][j] = diag2[i][j+1] + grid[i][j]
        for k in range(min(m, n), 0, -1):
            for i in range(m-k+1):
                for j in range(n-k+1):
                    s = row_psum[i][j+k] - row_psum[i][j]
                    ok = True
                    # Check all rows
                    for r in range(i, i+k):
                        if row_psum[r][j+k] - row_psum[r][j] != s:
                            ok = False
                            break
                    if not ok:
                        continue
                    # Check all columns
                    for c in range(j, j+k):
                        if col_psum[i+k][c] - col_psum[i][c] != s:
                            ok = False
                            break
                    if not ok:
                        continue
                    # Check main diagonal
                    if diag1[i+k][j+k] - diag1[i][j] != s:
                        continue
                    # Check anti-diagonal
                    if diag2[i+k][j] - diag2[i][j+k] != s:
                        continue
                    return k
        return 1
      
class Solution {
public:
    int largestMagicSquare(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> row_psum(m, vector<int>(n+1, 0)), col_psum(m+1, vector<int>(n, 0));
        vector<vector<int>> diag1(m+1, vector<int>(n+1, 0)), diag2(m+1, vector<int>(n+2, 0));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                row_psum[i][j+1] = row_psum[i][j] + grid[i][j];
                col_psum[i+1][j] = col_psum[i][j] + grid[i][j];
                diag1[i+1][j+1] = diag1[i][j] + grid[i][j];
                diag2[i+1][j] = diag2[i][j+1] + grid[i][j];
            }
        }
        for (int k = min(m, n); k >= 1; --k) {
            for (int i = 0; i + k <= m; ++i) {
                for (int j = 0; j + k <= n; ++j) {
                    int s = row_psum[i][j+k] - row_psum[i][j];
                    bool ok = true;
                    // Check rows
                    for (int r = i; r < i + k; ++r) {
                        if (row_psum[r][j+k] - row_psum[r][j] != s) {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) continue;
                    // Check columns
                    for (int c = j; c < j + k; ++c) {
                        if (col_psum[i+k][c] - col_psum[i][c] != s) {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) continue;
                    // Main diagonal
                    if (diag1[i+k][j+k] - diag1[i][j] != s) continue;
                    // Anti-diagonal
                    if (diag2[i+k][j] - diag2[i][j+k] != s) continue;
                    return k;
                }
            }
        }
        return 1;
    }
};
      
class Solution {
    public int largestMagicSquare(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] row_psum = new int[m][n+1];
        int[][] col_psum = new int[m+1][n];
        int[][] diag1 = new int[m+1][n+1];
        int[][] diag2 = new int[m+1][n+2];
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                row_psum[i][j+1] = row_psum[i][j] + grid[i][j];
                col_psum[i+1][j] = col_psum[i][j] + grid[i][j];
                diag1[i+1][j+1] = diag1[i][j] + grid[i][j];
                diag2[i+1][j] = diag2[i][j+1] + grid[i][j];
            }
        }
        for (int k = Math.min(m, n); k >= 1; --k) {
            for (int i = 0; i + k <= m; ++i) {
                for (int j = 0; j + k <= n; ++j) {
                    int s = row_psum[i][j+k] - row_psum[i][j];
                    boolean ok = true;
                    // Check rows
                    for (int r = i; r < i + k; ++r) {
                        if (row_psum[r][j+k] - row_psum[r][j] != s) {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) continue;
                    // Check columns
                    for (int c = j; c < j + k; ++c) {
                        if (col_psum[i+k][c] - col_psum[i][c] != s) {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) continue;
                    // Main diagonal
                    if (diag1[i+k][j+k] - diag1[i][j] != s) continue;
                    // Anti-diagonal
                    if (diag2[i+k][j] - diag2[i][j+k] != s) continue;
                    return k;
                }
            }
        }
        return 1;
    }
}
      
var largestMagicSquare = function(grid) {
    const m = grid.length, n = grid[0].length;
    const row_psum = Array.from({length: m}, () => Array(n+1).fill(0));
    const col_psum = Array.from({length: m+1}, () => Array(n).fill(0));
    const diag1 = Array.from({length: m+1}, () => Array(n+1).fill(0));
    const diag2 = Array.from({length: m+1}, () => Array(n+2).fill(0));
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            row_psum[i][j+1] = row_psum[i][j] + grid[i][j];
            col_psum[i+1][j] = col_psum[i][j] + grid[i][j];
            diag1[i+1][j+1] = diag1[i][j] + grid[i][j];
            diag2[i+1][j] = diag2[i][j+1] + grid[i][j];
        }
    }
    for (let k = Math.min(m, n); k >= 1; --k) {
        for (let i = 0; i + k <= m; ++i) {
            for (let j = 0; j + k <= n; ++j) {
                let s = row_psum[i][j+k] - row_psum[i][j];
                let ok = true;
                // Check rows
                for (let r = i; r < i + k; ++r) {
                    if (row_psum[r][j+k] - row_psum[r][j] !== s) {
                        ok = false;
                        break;
                    }
                }
                if (!ok) continue;
                // Check columns
                for (let c = j; c < j + k; ++c) {
                    if (col_psum[i+k][c] - col_psum[i][c] !== s) {
                        ok = false;
                        break;
                    }
                }
                if (!ok) continue;
                // Main diagonal
                if (diag1[i+k][j+k] - diag1[i][j] !== s) continue;
                // Anti-diagonal
                if (diag2[i+k][j] - diag2[i][j+k] !== s) continue;
                return k;
            }
        }
    }
    return 1;
};