给定一个二维矩阵,需要实现一个类支持多次查询子矩阵的元素和。子矩阵由左上角坐标(row1, col1)和右下角坐标(row2, col2)确定。要求实现两个方法:
使用二维前缀和预处理矩阵,将查询操作的时间复杂度优化为O(1)。
class NumMatrix:
def __init__(self, matrix: List[List[int]]):
if not matrix or not matrix[0]:
return
rows, cols = len(matrix), len(matrix[0])
# 创建前缀和矩阵,比原矩阵多一行一列
self.preSum = [[0] * (cols + 1) for _ in range(rows + 1)]
# 计算二维前缀和
for i in range(rows):
for j in range(cols):
# 当前格子(i,j)的前缀和 = 上方的前缀和 + 左方的前缀和 - 左上角的前缀和 + 当前值
self.preSum[i + 1][j + 1] = (self.preSum[i + 1][j] +
self.preSum[i][j + 1] -
self.preSum[i][j] +
matrix[i][j])
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
# 使用容斥原理计算区域和
return (self.preSum[row2 + 1][col2 + 1] -
self.preSum[row2 + 1][col1] -
self.preSum[row1][col2 + 1] +
self.preSum[row1][col1])考虑一个示例矩阵:
原矩阵:
3 0 1 4
5 6 3 2
1 2 0 1
4 1 0 1前缀和矩阵(第一行和第一列补0):
0 0 0 0 0
0 3 3 4 8
0 8 14 18 24
0 9 17 21 28
0 13 22 26 34当前格子(i,j)的前缀和计算公式是:
preSum[i + 1][j + 1] = preSum[i + 1][j] + preSum[i][j + 1] - preSum[i][j] + matrix[i][j]
为什么要用 i+1 和 j+1:
公式各部分含义是:
公式的推导:
当前位置的前缀和 = 上方的矩形区域 + 左方的矩形区域 - 重复计算的左上角区域 + 当前格子的值
3 0 1 4
5 6 3 2计算位置(1,1)(值为6)的前缀和:
这里:
为什么要这样计算:
使用这个前缀和的好处:
这个公式虽然看起来复杂,但理解了其中的原理后,就会发现这是一个非常巧妙的设计,能够高效地解决二维区域查询的问题。
对于原矩阵:
3 0 1 4
5 6 3 2
1 2 0 1
4 1 0 1让我们一步步计算前缀和矩阵:
0 0 0 0 0
0 * * * *
0 * * * *
0 * * * *
0 * * * *让我们逐个计算(标记计算的位置):
第一行第一个元素(0,0)对应的前缀和:
preSum[1][1] = preSum[1][0] + preSum[0][1] - preSum[0][0] + matrix[0][0]
= 0 + 0 - 0 + 3 = 3
0 0 0 0 0
0 3 * * *
0 * * * *
0 * * * *
0 * * * *继续第一行:
preSum[1][2] = preSum[1][1] + preSum[0][2] - preSum[0][1] + matrix[0][1]
= 3 + 0 - 0 + 0 = 3
preSum[1][3] = 3 + 0 - 0 + 1 = 4
preSum[1][4] = 4 + 0 - 0 + 4 = 8
结果:
0 0 0 0 0
0 3 3 4 8
0 * * * *
0 * * * *
0 * * * *计算第二行:
preSum[2][1] = preSum[2][0] + preSum[1][1] - preSum[1][0] + matrix[1][0]
= 0 + 3 - 0 + 5 = 8
preSum[2][2] = 8 + 3 - 3 + 6 = 14
preSum[2][3] = 14 + 4 - 3 + 3 = 18
preSum[2][4] = 18 + 8 - 4 + 2 = 24
结果:
0 0 0 0 0
0 3 3 4 8
0 8 14 18 24
0 * * * *
0 * * * *继续计算第三行:
preSum[3][1] = 0 + 8 - 0 + 1 = 9
preSum[3][2] = 9 + 14 - 8 + 2 = 17
preSum[3][3] = 17 + 18 - 14 + 0 = 21
preSum[3][4] = 21 + 24 - 18 + 1 = 28
结果:
0 0 0 0 0
0 3 3 4 8
0 8 14 18 24
0 9 17 21 28
0 * * * *最后计算第四行:
preSum[4][1] = 0 + 9 - 0 + 4 = 13
preSum[4][2] = 13 + 17 - 9 + 1 = 22
preSum[4][3] = 22 + 21 - 17 + 0 = 26
preSum[4][4] = 26 + 25 - 21 + 1 = 34
最终的前缀和矩阵:
0 0 0 0 0
0 3 3 4 8
0 8 17 21 27
0 9 17 21 28
0 13 22 26 34现在让我们验证一下查询区域(1,1)到(2,2)的和:
这个结果正确,因为原矩阵中这个区域的值是:
6 3
2 06 + 3 + 2 + 0 = 11
好的,让我用正确的2×2区域重新解释容斥原理。
对于原矩阵:
3 0 1 4
5 6 3 2
1 2 0 1
4 1 0 1要求位置(1,1)到(2,2)的区域和,即这个2×2区域:
6 3
2 0使用容斥原理计算的代码:
return (self.preSum[row2 + 1][col2 + 1] -
self.preSum[row2 + 1][col1] -
self.preSum[row1][col2 + 1] +
self.preSum[row1][col1])具体分析每个部分:
self.preSum[row2 + 1][col2 + 1] = self.preSum[3][3]
self.preSum[row2 + 1][col1] = self.preSum[3][1]
self.preSum[row1][col2 + 1] = self.preSum[1][3]
self.preSum[row1][col1] = self.preSum[1][1]
最终计算:
目标区域和 = 21 - 9 - 4 + 3 = 11
验证:实际区域 [6,3,2,0] 的和确实等于 11这就是容斥原理的应用:
这样就能得到我们想要的2×2区域的和。这种方法的优势是不管要求的区域有多大,都只需要对四个前缀和进行简单的加减运算就能得到结果。