题目来源:🔒308:二维区域和检索-矩阵可修改
问题抽象: 设计一个类 NumMatrix,用于高效处理 动态二维整数矩阵的任意子矩阵和查询与单点修改,满足以下核心需求:
-
类功能定义:
- 初始化:接收二维整数矩阵
matrix并构建支持动态操作的数据结构; - 更新方法:
update(int row, int col, int val)将matrix[row][col]的值修改为val; - 查询方法:
sumRegion(int row1, int col1, int row2, int col2)返回以(row1, col1)为左上角、(row2, col2)为右下角的矩形区域元素和(包含边界)。
- 初始化:接收二维整数矩阵
-
操作约束:
- 初始化效率:构造函数时间复杂度 O(mn)(允许预处理,
m×n为矩阵尺寸); - 更新高效:
update方法时间复杂度 O(log m × log n)(避免重构全矩阵); - 查询高效:
sumRegion方法时间复杂度 O(log m × log n)(避免扫描子矩阵); - 空间复杂度 O(mn):额外存储结构尺寸与输入矩阵相当。
- 初始化效率:构造函数时间复杂度 O(mn)(允许预处理,
-
输入输出:
- 输入矩阵
matrix:尺寸m×n(1 ≤ m, n ≤ 200),元素值-10^5 ≤ matrix[i][j] ≤ 10^5; - 更新参数:
0 ≤ row < m,0 ≤ col < n,新值val(整数); - 查询参数:
0 ≤ row1 ≤ row2 < m,0 ≤ col1 ≤ col2 < n; - 输出:区域和(整数)。
- 输入矩阵
-
边界处理:
- 空矩阵:
m=0或n=0时,更新/查询操作仍可执行(返回0或无操作); - 单点更新后查询:如
update(0,0,5)后sumRegion(0,0,0,0)返回5; - 大区域查询:如
sumRegion(0,0,m-1,n-1)返回矩阵总和; - 高频操作:支持
10^4次更新和查询操作(总调用次数上限)。
- 空矩阵:
类接口定义:
class NumMatrix {
public NumMatrix(int[][] matrix); // 初始化
public void update(int row, int col, int val); // 单点修改
public int sumRegion(int row1, int col1, int row2, int col2); // 区域和查询
}
应用场景:实时更新的网格数据统计(如动态图表、游戏地图状态监控)。
解题思路
核心问题:在动态更新的二维矩阵中高效计算任意矩形区域的和,需同时优化 update 和 sumRegion 操作的时间复杂度。
算法选型:二维树状数组(Binary Indexed Tree)
-
为什么选择树状数组?
传统二维前缀和在更新时需要O(mn)时间重建,不适用于频繁更新场景。树状数组支持:- 单点更新:时间复杂度
O(log m * log n)。 - 区间求和:时间复杂度
O(log m * log n)。
适合本题更新和查询均匀调用的场景。
- 单点更新:时间复杂度
-
二维树状数组原理:
- 数据结构:维护一个二维数组
tree,其中tree[i][j]存储从(i - lowbit(i) + 1, j - lowbit(j) + 1)到(i, j)的矩形区域和(lowbit(x) = x & -x)。 - 更新操作:当修改位置
(row, col)的值时,需更新所有包含该位置的树状数组区域。通过迭代i = row + 1到N(每次i += lowbit(i)),并在每个i中迭代j = col + 1到M(每次j += lowbit(j)),更新tree[i][j]。 - 求和操作:计算
(0, 0)到(row, col)的二维前缀和时,逆向迭代(i = row + 1到1,每次i -= lowbit(i);j = col + 1到1,每次j -= lowbit(j)),累加tree[i][j]。 - 区域和计算:利用二维前缀和的容斥原理:
sumRegion(row1, col1, row2, col2) = sum(row2, col2) - sum(row1 - 1, col2) - sum(row2, col1 - 1) + sum(row1 - 1, col1 - 1)
- 数据结构:维护一个二维数组
时间复杂度: 初始化:O(mn * log m * log n),每个元素需更新树状数组。update:O(log m * log n)。sumRegion:O(log m * log n)。
代码实现(Java版)🔥点击下载源码
class NumMatrix {
private int[][] tree; // 二维树状数组
private int[][] nums; // 原始矩阵
private int N; // 矩阵行数
private int M; // 矩阵列数
public NumMatrix(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) return;
N = matrix.length;
M = matrix[0].length;
nums = new int[N][M];
tree = new int[N + 1][M + 1]; // 树状数组下标从1开始
// 初始化:逐个更新元素到树状数组
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
update(i, j, matrix[i][j]);
}
}
}
// 更新操作:将 matrix[row][col] 设为 val
public void update(int row, int col, int val) {
if (N == 0 || M == 0) return;
int delta = val - nums[row][col]; // 计算增量
nums[row][col] = val; // 更新原始矩阵
// 更新所有包含 (row, col) 的树状数组区域
for (int i = row + 1; i <= N; i += lowbit(i)) {
for (int j = col + 1; j <= M; j += lowbit(j)) {
tree[i][j] += delta;
}
}
}
// 计算 (0,0) 到 (row, col) 的二维前缀和
public int sum(int row, int col) {
int total = 0;
// 逆向迭代:从 (row+1, col+1) 向左上累加
for (int i = row + 1; i > 0; i -= lowbit(i)) {
for (int j = col + 1; j > 0; j -= lowbit(j)) {
total += tree[i][j];
}
}
return total;
}
// 计算区域和:利用二维前缀和容斥
public int sumRegion(int row1, int col1, int row2, int col2) {
if (N == 0 || M == 0) return 0;
return sum(row2, col2)
- sum(row1 - 1, col2)
- sum(row2, col1 - 1)
+ sum(row1 - 1, col1 - 1);
}
// 辅助函数:计算 x 的二进制最低位1的值
private int lowbit(int x) {
return x & (-x);
}
}
代码说明
-
树状数组下标处理:
- 树状数组下标从
1开始,因此实际使用时需将行列+1(如row + 1)。 lowbit(x)函数用于快速计算二进制最低位1的值(如lowbit(6) = 2)。
- 树状数组下标从
-
更新操作 (
update):- 计算新值与旧值的增量
delta。 - 通过双重循环更新所有覆盖
(row, col)的树状数组节点。外层循环沿行更新(i += lowbit(i)),内层循环沿列更新(j += lowbit(j))。
- 计算新值与旧值的增量
-
求和操作 (
sumRegion):- 先计算
(0, 0)到(row, col)的二维前缀和sum(row, col),通过逆向迭代(i -= lowbit(i),j -= lowbit(j))累加树状数组的值。 - 区域和通过四个前缀和的组合计算:
其中 B=(row2,col2), A=(row1-1,col2), C=(row2,col1-1), D=(row1-1,col1-1)sumRegion = sum(B) - sum(A) - sum(C) + sum(D)
- 先计算
-
边界处理:
- 若
row1 = 0或col1 = 0,则sum(row1 - 1, ...)自动返回0(因循环条件i > 0)。
- 若

833

被折叠的 条评论
为什么被折叠?



