二维前缀和知识讲解+例题

1.二维前缀和

二维前缀和是一种数组处理技术,它在处理二维数据(如矩阵)时非常有用。它的概念源自于一维前缀和,但扩展到了两个维度。二维前缀和的主要思想是将矩阵中的每个元素与其上方和左方的元素进行累加,从而快速计算出矩阵中任意子矩阵的元素和。

定义如下:

设有一个二维矩阵 A,其大小为 m x n,即包含 m 行和 n 列。矩阵 A 的元素表示为 A[i][j],其中 i 表示行索引,j 表示列索引。

二维前缀和矩阵 P 也具有相同的大小 m x n,并且定义如下:

  • 对于第一行和第一列的元素,P[i][j] 等于 A[i][j]i = 0j = 0)。
  • 对于其他位置的元素,P[i][j] 计算为其下方元素 A[i-1][j]、左方元素 A[i][j-1] 以及左下方对角线元素 A[i-1][j-1] 的和,即 P[i][j] = A[i][j] + A[i-1][j] + A[i][j-1] - A[i-1][j-1]i > 0j > 0)。
  • 要求右下角下标为i,j的前缀和(红色框),只需用蓝色框的前缀和加上绿色框的前缀和,再减去重复部分,最后加上A[i][j]位置。

通过这种方式,我们可以快速地计算出矩阵中任意子矩阵的和,而不需要进行逐个元素的累加。这是因为对于子矩阵 (i, j, k, l)(其中 i <= kj <= l),其和可以通过以下公式计算得到:

Sum = P[k][l] - P[i-1][l] - P[k][j-1] + P[i-1][j-1]

这种技术在很多算法问题中都非常有用,特别是在需要频繁计算子矩阵和的场景中,如动态规划、图像处理和数据流问题等。通过预处理二维前缀和,可以将原本复杂度较高的计算简化,提高算法的效率。

2.例题(leetcode-1139.最大的以 1 为边界的正方形)

给你一个由若干 0 和 1 组成的二维网格 grid,请你找出边界全部由 1 组成的最大 正方形 子网格,并返回该子网格中的元素数量。如果不存在,则返回 0。

示例 1:

输入:grid = [[1,1,1],[1,0,1],[1,1,1]]
输出:9
示例 2:

输入:grid = [[1,1,0,0]]
输出:1

思路

这个问题可以通过构建一个二维前缀和矩阵来高效解决。

代码中定义了一个 Solution 类,其中包含了解决这个问题的主要方法和辅助方法:

  1. largest1BorderedSquare 方法:这是公共接口方法,它接收一个二维数组 grid 作为输入,并返回最大边界正方形的边长。它首先初始化 mn 为网格的行数和列数,然后调用 build 方法来执行实际的计算。

  2. build 方法:这是私有的辅助方法,它负责构建二维前缀和矩阵,并在此基础上寻找最大的边界正方形。它首先通过嵌套循环遍历整个网格,使用 getValue 方法来计算每个元素的前缀和。然后,它检查网格的右下角元素,如果为 0,则直接返回 0,表示没有边界正方形。否则,它继续寻找最大的边界正方形。

  3. getSum 方法:这是另一个私有的辅助方法,用于计算给定的子矩阵(由坐标 a, b, c, d 确定)的元素和。它使用前缀和矩阵 grid 来快速得到子矩阵的和。

  4. getValue 方法:这也是一个私有的辅助方法,用于获取网格中位置 (i, j) 的元素值。如果索引超出范围,则返回 0。

build 方法中,代码使用了一个双层循环来遍历网格,并尝试找到最大的边界正方形。对于每个位置 (i, j),它尝试扩展一个正方形,直到正方形的边界上出现 0。在尝试扩展正方形时,它使用 getSum 方法来计算当前正方形的元素和,并与预期的和((k - 1) << 2,其中 k 是正方形的边长)进行比较。如果两者相等,说明找到了一个边界正方形,并且更新最大边长 ans

最后,build 方法返回最大边界正方形的边长的平方,这是因为题目要求的是边长,而不是面积。

整体上,这段代码通过构建和利用二维前缀和矩阵,以及一系列的边界检查和子矩阵和计算,高效地解决了寻找最大边界正方形的问题。

3.代码

class Solution {
    public int m;
    public int n;

    public int largest1BorderedSquare(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        return build(grid);
    }

    public int build(int[][] grid) {
        //获取前缀和
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = getValue(i - 1, j, grid) + getValue(i, j - 1, grid) - getValue(i - 1, j - 1, grid) + getValue(i, j, grid);
            }
        }
        if (grid[m - 1][n - 1] == 0) {
            return 0;
        }
        int ans = 1;
        for (int a = 0; a < m; a++) {
            for (int b = 0; b < n; b++) {
                //左上角遍历
                for (int c = a + ans, d = b + ans, k = ans + 1; c < m && d < n; c++, d++, k++) {
                    if (getSum(a, b, c, d, grid) - getSum(a + 1, b + 1, c - 1, d - 1, grid) == ((k - 1) << 2)) {
                        ans = k;
                    }
                }
            }
        }
        return ans * ans;
    }

    public int getSum(int a, int b, int c, int d, int[][] grid) {
        if (a > c) return 0;
        return grid[c][d] - getValue(c, b - 1, grid) - getValue(a - 1, d, grid) + getValue(a - 1, b - 1, grid);
    }

    public int getValue(int i, int j, int[][] grid) {
        return i < 0 || j < 0 ? 0 : grid[i][j];
    }
}

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值