DEV Community

Cover image for 🧩 Beginner-Friendly Guide 'Largest Magic Square' – LeetCode 1895 (C++, Python, JavaScript)
Om Shree
Om Shree

Posted on

🧩 Beginner-Friendly Guide 'Largest Magic Square' – LeetCode 1895 (C++, Python, JavaScript)

Searching for patterns in a grid is a classic challenge in algorithmic thinking. In this problem, we are tasked with finding the largest possible sub-grid where every row, column, and both main diagonals add up to the exact same value.

You're given:

  • An integer grid.
  • A definition of a Magic Square: a sub-grid where the sum of every row, every column, and both diagonals are equal.

Your goal:

  • Find and return the maximum side length of any magic square hidden within the grid.

Example 1 :

Image description ex1
Input: grid = [[7,1,4,5,6],[2,5,1,6,4],[1,5,4,3,2],[1,2,7,3,4]]
Output: 3
Explanation: The largest magic square has a size of 3.
Every row sum, column sum, and diagonal sum of this magic square is equal to 12.

  • Row sums: 5+1+6 = 5+4+3 = 2+7+3 = 12
  • Column sums: 5+5+2 = 1+4+7 = 6+3+3 = 12
  • Diagonal sums: 5+4+3 = 6+4+2 = 12

Example 2 :

Image description ex2
Input: grid = [[5,1,3,1],[9,3,3,1],[1,3,3,8]]
Output: 2

Constrains :

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • 1 <= grid[i][j] <= 106

Intuition

The brute-force approach would be to check every possible square of every size and manually sum up its rows, columns, and diagonals. However, that involves a lot of repeated work. To make this efficient, we use a technique called Prefix Sums.

Think of a prefix sum like a "running total." If you know the total sum of a row from the start up to index 10, and the total sum up to index 5, you can find the sum of the elements between 5 and 10 instantly by subtracting the two totals.

In this solution, we pre-calculate four types of running totals for every cell :

  1. Horizontal: Sum of elements in that row.
  2. Vertical: Sum of elements in that column.
  3. Main Diagonal: Sum of elements moving from top-left to bottom-right.
  4. Anti-Diagonal: Sum of elements moving from top-right to bottom-left.

Once we have these tables, checking if a square is "magic" becomes a matter of simple subtraction rather than looping through every single cell. We start searching from the largest possible side length (the minimum of and ) and work our way down. The first one that satisfies the magic square condition is our answer.


Code Blocks

C++

class Solution {
public:
    bool isMagic(vector<vector<array<int,4>>> const & prefixSum, int r, int c, int sz) {
        // Calculate the main diagonal sum
        int targetSum = prefixSum[r+sz][c+sz][2] - prefixSum[r][c][2]; 

        // Check the anti-diagonal sum
        if (targetSum != prefixSum[r+sz][c+1][3] - prefixSum[r][c+sz+1][3]) {
            return false;
        }

        // Check all row sums within the square
        for (int j = r; j < r + sz; j++) {
            if (targetSum != prefixSum[j+1][c+sz][0] - prefixSum[j+1][c][0]) {
                return false;
            }
        }

        // Check all column sums within the square
        for (int j = c; j < c + sz; j++) {
            if (targetSum != prefixSum[r+sz][j+1][1] - prefixSum[r][j+1][1]) {
                return false;
            }
        }

        return true;
    }

    int largestMagicSquare(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        // prefixSum stores: [row, col, diag, anti-diag] sums
        vector<vector<array<int,4>>> prefixSum(m + 1, vector<array<int,4>>(n + 2));

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                int val = grid[i-1][j-1];
                prefixSum[i][j][0] = prefixSum[i][j-1][0] + val; // Row
                prefixSum[i][j][1] = prefixSum[i-1][j][1] + val; // Col
                prefixSum[i][j][2] = prefixSum[i-1][j-1][2] + val; // Diag
                prefixSum[i][j][3] = prefixSum[i-1][j+1][3] + val; // Anti-Diag
            }
        }

        for (int k = min(m, n); k >= 2; k--) {
            for (int i = 0; i <= m - k; i++) {
                for (int j = 0; j <= n - k; j++) {
                    if (isMagic(prefixSum, i, j, k)) return k;
                }
            }
        }
        return 1;
    }
};

Enter fullscreen mode Exit fullscreen mode

Python

class Solution:
    def largestMagicSquare(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])

        # prefixSum[i][j] stores [row, col, diag, anti_diag]
        # We add padding to handle boundary conditions easily
        pref = [[[0] * 4 for _ in range(n + 2)] for _ in range(m + 1)]

        for r in range(1, m + 1):
            for c in range(1, n + 1):
                val = grid[r-1][c-1]
                pref[r][c][0] = pref[r][c-1][0] + val
                pref[r][c][1] = pref[r-1][c][1] + val
                pref[r][c][2] = pref[r-1][c-1][2] + val
                pref[r][c][3] = pref[r-1][c+1][3] + val

        def is_magic(r, c, k):
            # Target sum from the main diagonal
            target = pref[r+k][c+k][2] - pref[r][c][2]

            # Check anti-diagonal
            if target != pref[r+k][c+1][3] - pref[r][c+k+1][3]:
                return False

            # Check all rows
            for i in range(r, r + k):
                if pref[i+1][c+k][0] - pref[i+1][c][0] != target:
                    return False

            # Check all columns
            for j in range(c, c + k):
                if pref[r+k][j+1][1] - pref[r][j+1][1] != target:
                    return False

            return True

        # Check from largest possible side length downwards
        for k in range(min(m, n), 1, -1):
            for i in range(m - k + 1):
                for j in range(n - k + 1):
                    if is_magic(i, j, k):
                        return k
        return 1

Enter fullscreen mode Exit fullscreen mode

JavaScript

/**
 * @param {number[][]} grid
 * @return {number}
 */
var largestMagicSquare = function(grid) {
    const m = grid.length;
    const n = grid[0].length;

    // Create prefix sum 3D array: [m+1][n+2][4]
    const pref = Array.from({ length: m + 1 }, () => 
        Array.from({ length: n + 2 }, () => new Int32Array(4))
    );

    for (let r = 1; r <= m; r++) {
        for (let c = 1; c <= n; c++) {
            const val = grid[r-1][c-1];
            pref[r][c][0] = pref[r][c-1][0] + val;     // Row
            pref[r][c][1] = pref[r-1][c][1] + val;     // Col
            pref[r][c][2] = pref[r-1][c-1][2] + val;   // Diag
            pref[r][c][3] = pref[r-1][c+1][3] + val;   // Anti-Diag
        }
    }

    const isMagic = (r, c, k) => {
        const target = pref[r+k][c+k][2] - pref[r][c][2];

        if (target !== pref[r+k][c+1][3] - pref[r][c+k+1][3]) return false;

        for (let i = r; i < r + k; i++) {
            if (pref[i+1][c+k][0] - pref[i+1][c][0] !== target) return false;
        }

        for (let j = c; j < c + k; j++) {
            if (pref[r+k][j+1][1] - pref[r][j+1][1] !== target) return false;
        }

        return true;
    };

    for (let k = Math.min(m, n); k >= 2; k--) {
        for (let i = 0; i <= m - k; i++) {
            for (let j = 0; j <= n - k; j++) {
                if (isMagic(i, j, k)) return k;
            }
        }
    }

    return 1;
};

Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Prefix Sums in 2D: This technique is a powerhouse for range-sum queries in grids, reducing the time complexity of checking a sub-grid from to for each row or column.
  • Space-Time Tradeoff: By using extra memory to store our prefixSum tables, we significantly speed up the validation process for each potential square.
  • Greedy Optimization: Searching from the largest possible down to 2 allows us to return the answer immediately upon finding a match, saving unnecessary computations.

Final Thoughts

As a mentor, I often see students struggle with grid problems because they try to "count" everything manually. Learning to use prefix sums is like leveling up your vision in game development or data processing: you stop seeing individual pixels and start seeing regions. This problem is excellent practice for interviews at companies like Google or Amazon, where multidimensional array manipulation and optimization are frequently tested. In the real world, these concepts are the foundation for image processing filters and spatial data analysis.

Top comments (0)