最大的幻方

标签: 数组 矩阵 前缀和

难度: Medium

一个 k x k 的 幻方 指的是一个 k x k 填满整数的方格阵,且每一行、每一列以及两条对角线的和 全部相等 。幻方中的整数 不需要互不相同 。显然,每个 1 x 1 的方格都是一个幻方。

给你一个 m x n 的整数矩阵 grid ,请你返回矩阵中 最大幻方 的 尺寸 (即边长 k)。

 

示例 1:

输入:grid = [[7,1,4,5,6],[2,5,1,6,4],[1,5,4,3,2],[1,2,7,3,4]]
输出:3
解释:最大幻方尺寸为 3 。
每一行,每一列以及两条对角线的和都等于 12 。
- 每一行的和:5+1+6 = 5+4+3 = 2+7+3 = 12
- 每一列的和:5+5+2 = 1+4+7 = 6+3+3 = 12
- 对角线的和:5+4+3 = 6+4+2 = 12

示例 2:

输入:grid = [[5,1,3,1],[9,3,3,1],[1,3,3,8]]
输出:2

 

提示:

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

Submission

运行时间: 108 ms

内存: 16.4 MB

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

        rowsum = [[0] * n for _ in range(m)]
        for i in range(m):
            rowsum[i][0] = grid[i][0]
            for j in range(1, n):
                rowsum[i][j] = rowsum[i][j - 1] + grid[i][j]
        

        colsum = [[0] * n for _ in range(m)]
        for j in range(n):
            colsum[0][j] = grid[0][j]
            for i in range(1, m):
                colsum[i][j] = colsum[i - 1][j] + grid[i][j]


        for edge in range(min(m, n), 1, -1):

            for i in range(m - edge + 1):
                for j in range(n - edge + 1):
                    stdsum = rowsum[i][j + edge - 1] - (0 if j == 0 else rowsum[i][j - 1])
                    check = True
                    for ii in range(i + 1, i + edge):
                        if rowsum[ii][j + edge - 1] - (0 if j == 0 else rowsum[ii][j - 1]) != stdsum:
                            check = False
                            break
                    if not check:
                        continue

                    for jj in range(j, j + edge):
                        if colsum[i + edge - 1][jj] - (0 if i == 0 else colsum[i - 1][jj]) != stdsum:
                            check = False
                            break
                    if not check:
                        continue
                    

                    d1 = d2 = 0

                    for k in range(edge):
                        d1 += grid[i + k][j + k]
                        d2 += grid[i + k][j + edge - 1 - k]
                    if d1 == stdsum and d2 == stdsum:
                        return edge

        return 1

Explain

这个解决方案首先计算了矩阵的行和列前缀和,以便快速计算任何子矩阵的行和列的和。然后,它尝试找到最大的幻方,从最大可能的边长开始,逐步减小边长,直到找到一个有效的幻方或者边长降到1。对于每个可能的边长,它会遍历所有可能的起始点,检查是否所有行、列以及对角线的和都等于第一行的和。如果找到这样的幻方,则立即返回这个边长。如果没有找到,最后返回1(因为任何1x1的子矩阵都是幻方)。

时间复杂度: O(mn + min(m, n)^3)

空间复杂度: O(mn)

class Solution:
    def largestMagicSquare(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        
        # Calculate row prefix sums
        rowsum = [[0] * n for _ in range(m)]
        for i in range(m):
            rowsum[i][0] = grid[i][0]
            for j in range(1, n):
                rowsum[i][j] = rowsum[i][j - 1] + grid[i][j]
        
        # Calculate column prefix sums
        colsum = [[0] * n for _ in range(m)]
        for j in range(n):
            colsum[0][j] = grid[0][j]
            for i in range(1, m):
                colsum[i][j] = colsum[i - 1][j] + grid[i][j]
        
        # Check all possible sizes of magic squares from largest to smallest
        for edge in range(min(m, n), 1, -1):
            for i in range(m - edge + 1):
                for j in range(n - edge + 1):
                    # Check if all rows have the same sum
                    stdsum = rowsum[i][j + edge - 1] - (0 if j == 0 else rowsum[i][j - 1])
                    check = True
                    for ii in range(i + 1, i + edge):
                        if rowsum[ii][j + edge - 1] - (0 if j == 0 else rowsum[ii][j - 1]) != stdsum:
                            check = False
                            break
                    if not check:
                        continue
                    
                    # Check if all columns have the same sum
                    for jj in range(j, j + edge):
                        if colsum[i + edge - 1][jj] - (0 if i == 0 else colsum[i - 1][jj]) != stdsum:
                            check = False
                            break
                    if not check:
                        continue
                    
                    # Calculate the sums of both diagonals
                    d1 = d2 = 0
                    for k in range(edge):
                        d1 += grid[i + k][j + k]
                        d2 += grid[i + k][j + edge - 1 - k]
                    if d1 == stdsum and d2 == stdsum:
                        return edge
        
        return 1

Explore

选择从最大可能的边长开始递减检查的原因是为了尽快找到最大的幻方。如果从小到大增加边长进行检查,我们可能需要检查很多较小的边长幻方,直到达到最大边长。而从最大边长开始检查,则一旦找到一个有效的幻方,就可以立即返回这个边长,无需检查更小的边长,从而节省时间和计算资源。

如果在检查过程中发现某行或某列的和不符合标准和,那么没有必要继续检查剩余的行和列。这是因为幻方的定义要求所有行、所有列以及对角线的和必须完全相等。一旦发现任何一行或一列不符合这一条件,当前的子矩阵就不可能是一个有效的幻方,因此可以立即停止当前的检查,继续尝试其他可能的子矩阵,这样做可以提高算法的效率。

在计算对角线和的代码中,对角线元素的索引是通过循环变量`k`和边长`edge`来控制的。循环变量`k`从0开始,直到`edge-1`。对于主对角线,使用的索引是`grid[i + k][j + k]`,对于副对角线是`grid[i + k][j + edge - 1 - k]`。由于算法先检查了边长`edge`是否小于等于`m`和`n`(矩阵的行数和列数),保证了`i + edge <= m`和`j + edge <= n`,因此在索引时不会超出矩阵的边界,从而避免了索引越界的错误。

在初始化前缀和数组`rowsum`和`colsum`时,选择先单独处理第一个元素,再处理剩余元素的原因是为了设置基础值以便计算累加和。对于`rowsum`,第一个元素是直接从`grid`中获取的,之后的元素是基于前一个元素的值加上当前`grid`元素的值;对于`colsum`也是类似的处理。这种方式简化了累加过程,避免了在循环内部频繁地检查索引是否为0,从而提高了代码的简洁性和执行效率。