子矩阵的和
子矩阵的和
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问,输出子矩阵中所有数的和。
所用方法和基本原理
- 二维前缀和数组构建:
- 定义了
getPrefixSum方法用于生成二维前缀和数组s。二维前缀和数组s[i][j]代表从矩阵左上角(0, 0)到位置(i, j)所构成子矩阵的元素总和。 - 初始化
s[0][0] = 0,因为空矩阵的元素和为0。 - 通过两层嵌套循环遍历原矩阵
arr。对于每个位置(i, j),利用公式s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + arr[i][j]来计算前缀和。这里,s[i - 1][j]是上方子矩阵的和,s[i][j - 1]是左方子矩阵的和,由于这两个子矩阵有重叠部分(左上角为(i - 1, j - 1)的子矩阵),所以要减去s[i - 1][j - 1],再加上当前位置的元素arr[i][j],从而得到从(0, 0)到(i, j)的子矩阵的元素总和。
- 定义了
- 子矩阵和计算:
- 对于每个询问给出的子矩阵
(x1, y1, x2, y2),借助已构建的二维前缀和数组s计算其和。计算公式为s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]。 s[x2][y2]是从矩阵左上角(0, 0)到右下角(x2, y2)的子矩阵的和。减去s[x1 - 1][y2]可去掉所求子矩阵左边部分的和,减去s[x2][y1 - 1]可去掉所求子矩阵上边部分的和。但这样会多减去一次左上角为(x1 - 1, y1 - 1)的子矩阵(即重叠部分),所以要加上s[x1 - 1][y1 - 1],最终得到以(x1, y1)为左上角、(x2, y2)为右下角的子矩阵的和。
- 对于每个询问给出的子矩阵
代码及注释
import java.util.Scanner;
public class SubMatrixSum {
// 生成二维前缀和数组
public static int[][] getPrefixSum(int[][] arr) {
int[][] s = new int[arr.length + 1][arr[0].length + 1];
for (int i = 1; i <= arr.length; i++) {
for (int j = 1; j <= arr[0].length; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + arr[i - 1][j - 1];
}
}
return s;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] arr = new int[n][m];
// 输入矩阵元素
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
arr[i][j] = sc.nextInt();
}
}
int q = sc.nextInt();
int[][] s = getPrefixSum(arr);
// 处理每个查询
for (int i = 0; i < q; i++) {
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
// 计算并输出子矩阵的和
System.out.println(s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
sc.close();
}
}
举例说明
假设有一个 3×3 的矩阵,实际构建前缀和数组时按 4×4 处理(多一行一列用于边界处理,值都为 0):
原矩阵 arr:
| 1 | 2 | 3 | |
| 4 | 5 | 6 | |
| 7 | 8 | 9 |
-
构建二维前缀和数组:
- 初始化
s为4×4的数组,所有元素初始值为0。 - 计算前缀和:
s[1][1] = s[0][1] + s[1][0] - s[0][0] + arr[0][0] = 0 + 0 - 0 + 1 = 1s[1][2] = s[0][2] + s[1][1] - s[0][1] + arr[0][1] = 0 + 1 - 0 + 2 = 3s[1][3] = s[0][3] + s[1][2] - s[0][2] + arr[0][2] = 0 + 3 - 0 + 3 = 6s[2][1] = s[1][1] + s[2][0] - s[1][0] + arr[1][0] = 1 + 0 - 0 + 4 = 5s[2][2] = s[1][2] + s[2][1] - s[1][1] + arr[1][1] = 3 + 5 - 1 + 5 = 12s[2][3] = s[1][3] + s[2][2] - s[1][2] + arr[1][2] = 6 + 12 - 3 + 6 = 21s[3][1] = s[2][1] + s[3][0] - s[2][0] + arr[2][0] = 5 + 0 - 0 + 7 = 12s[3][2] = s[2][2] + s[3][1] - s[2][1] + arr[2][1] = 12 + 12 - 5 + 8 = 27s[3][3] = s[2][3] + s[3][2] - s[2][2] + arr[2][2] = 21 + 27 - 12 + 9 = 45
二维前缀和数组
s为:0 0 0 0 0 1 3 6 0 5 12 21 0 12 27 45 - 初始化
-
计算查询的子矩阵和:
- 对于查询
(1, 1, 2, 2),x1 = 1,y1 = 1,x2 = 2,y2 = 2。 s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] = s[2][2] - s[0][2] - s[2][0] + s[0][0] = 12 - 0 - 0 + 0 = 12
- 对于查询
方法的优劣
- 时间复杂度:
- 预处理阶段:构建二维前缀和数组时,有两层嵌套循环,每层循环的次数分别为
n + 1和m + 1,时间复杂度为 $O((n + 1)×(m + 1))$,简化后为 $O(n×m)$,其中n是矩阵的行数,m是矩阵的列数。 - 查询阶段:对于
q个查询,每个查询只需进行固定次数的数组访问和加减法运算,时间复杂度为 $O(1)$,因此查询阶段的总时间复杂度为 $O(q)$。综合来看,整体时间复杂度为 $O(n×m + q)$。相较于每次查询都遍历子矩阵所有元素的暴力解法(时间复杂度为 $O(q×n×m)$),此方法在处理多个查询时效率大幅提高。
- 预处理阶段:构建二维前缀和数组时,有两层嵌套循环,每层循环的次数分别为
- 空间复杂度:
需要额外创建一个(n + 1)×(m + 1)的二维数组s来存储前缀和,所以空间复杂度为 $O((n + 1)×(m + 1))$,简化后为 $O(n×m)$。
优点:
- 对于多个子矩阵和的查询操作,效率很高,能够快速得出结果。
- 算法思路明确,基于二维前缀和的计算逻辑相对容易理解和实现。
缺点:
- 空间开销较大,需要额外的空间来存储前缀和数组,在处理大规模矩阵时可能面临内存不足的问题。
- 如果矩阵元素频繁变动,每次变动后都需要重新计算二维前缀和数组,维护成本较高。

浙公网安备 33010602011771号