2.前缀和与差分(一维和二维)
0、参考资料(讲解视频及博客等)
- 本人水平有限,如有错误,恳请指正
- 【【C++算法基础】#9前缀和与差分 | 轻松学算法 | 图解ACM竞赛算法】https://www.bilibili.com/video/BV1NX4y147G5?vd_source=d40f68483c8501ae1703de6cc0b5b7d1
- 【基础算法之二维前缀和】https://www.bilibili.com/video/BV12w4m1f77G?vd_source=d40f68483c8501ae1703de6cc0b5b7d1
一、应用场景
前缀和算法
- 区间和查询
- 例如:计算数组中任意区间
[l, r]的和,时间复杂度从O(n)优化为O(1)。
- 例如:计算数组中任意区间
- 子数组问题
- 统计满足条件的子数组数量(如和为
K、可被K整除等)。
- 统计满足条件的子数组数量(如和为
- 二维矩阵区域和
- 快速计算矩阵中任意矩形区域的和,时间复杂度从
O(mn)优化为O(1)。
- 快速计算矩阵中任意矩形区域的和,时间复杂度从
差分算法
- 区间修改
- 对数组的某个区间
[l, r]进行加减操作,时间复杂度从O(n)优化为O(1)。 - 例如:多次区间加减操作后的最终数组计算。
- 对数组的某个区间
- 多场景应用
- 适用于需要频繁修改区间值的场景,如温度变化、流量统计等。
二、核心思想/步骤
前缀和
一维:
- 构建前缀和数组:
s[i] = s[i-1] + a[i] - 区间和计算:`sum[l..r] = s[r] - s[l-1]
二维: - 构建前缀和数组:
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j] - 子矩阵和计算:`sum(x1,y1,x2,y2) = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]
差分
一维:
- 构建差分数组:
b[i] = a[i] - a[i-1](其中b[1]=a[1]) - 区间修改:
b[l] += c; b[r+1] -= c - 构建差分矩阵:
b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1] - 子矩阵修改:
b[x1][y1] += c; b[x1][y2+1] -= c; b[x2+1][y1] -= c; b[x2+1][y2+1] += c
三、代码模板与测试
代码模板 (java 为例)
一维前缀和
class PrefixSum {
private int[] prefix;
//构造前缀和
public PrefixSum(int[] nums) {
int n = nums.length;
prefix = new int[n + 1];//prefix[i]表示nums前i-1元素和
for (int i = 1; i <= n; i++) {
prefix[i] = prefix[i - 1] + nums[i - 1];
}
}
//利用前缀和查询区间和
public int sumRange(int l, int r) {//这里是索引
return prefix[r + 1] - prefix[l];
}
}
二维前缀和
class MatrixPrefixSum {
private int[][] prefix;
//构造前缀和
public MatrixPrefixSum(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
prefix = new int[m + 1][n + 1];//prefix[[i][j]表示nums含(i-1,j-1)左上角元素和
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
}
}
}
public int sumRegion(int r1, int c1, int r2, int c2) {//这里是索引
return prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1];
}
}
样例
class PrefixSum {
private int[] prefix;
//构造前缀和
public PrefixSum(int[] nums) {
int n = nums.length;
prefix = new int[n + 1];//prefix[i]表示nums前i-1元素和
for (int i = 1; i <= n; i++) {
prefix[i] = prefix[i - 1] + nums[i - 1];
}
}
//利用前缀和查询区间和
public int sumRange(int l, int r) {//这里是索引
return prefix[r + 1] - prefix[l];
}
}
class MatrixPrefixSum {
private int[][] prefix;
//构造前缀和
public MatrixPrefixSum(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
prefix = new int[m + 1][n + 1];//prefix[[i][j]表示nums含(i-1,j-1)左上角元素和
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
}
}
}
public int sumRegion(int r1, int c1, int r2, int c2) {//这里是索引
return prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1];
}
}
public class Main {
public static void main(String[] args) {
int []nums = new int[]{1,2,3};
int [][]matrix = new int[][]{
{1,2,3},
{4,5,6},
{7,8,9}
};
PrefixSum m = new PrefixSum(nums);
System.out.println(m.prefix[1]);
System.out.println(m.sumRange(1, 2));
MatrixPrefixSum m2 = new MatrixPrefixSum(matrix);
System.out.println(m2.prefix[1][1]);
System.out.println(m2.sumRegion(1, 1, 2, 2));
}
}
一维差分
// 一维差分数组类
class DiffArray {
public int[] diff; // 差分数组
/**
* 构造函数:通过传入的数组初始化差分数组。
* @param nums 原始数组
*/
public DiffArray(int[] nums) {
int n = nums.length;
diff = new int[n + 1]; // 长度为 n+1,方便边界处理
diff[0] = nums[0]; // 第一个元素直接赋值
for (int i = 1; i < n; i++) {
diff[i] = nums[i] - nums[i - 1]; // 计算差分
}
}
/**
* 更新范围 [l, r] 内的所有元素,增加值 val。
* @param l 起始索引
* @param r 结束索引
* @param val 增加的值
*/
public void updateRange(int l, int r, int val) {
diff[l] += val; // 左边界增加 val
if (r + 1 < diff.length) {
diff[r + 1] -= val; // 右边界后一位减少 val
}
}
/**
* 根据差分数组还原原始数组。
* @return 还原后的数组
*/
public int[] getResult() {
int[] res = new int[diff.length - 1];
res[0] = diff[0]; // 第一个元素直接赋值
for (int i = 1; i < res.length; i++) {
res[i] = res[i - 1] + diff[i]; // 累加还原
}
return res;
}
}
二维差分
// 二维差分数组类
class DiffArray2D {
private int[][] diff; // 差分数组
private int rows, cols; // 原始矩阵的行数和列数
/**
* 构造函数:通过传入的 matrix 初始化差分数组。
* @param matrix 原始二维数组
*/
public DiffArray2D(int[][] matrix) {
this.rows = matrix.length;
this.cols = matrix[0].length;
this.diff = new int[rows][cols];
// 初始化差分数组
diff[0][0] = matrix[0][0]; // 左上角元素直接赋值
for (int j = 1; j < cols; j++) {
diff[0][j] = matrix[0][j] - matrix[0][j - 1]; // 第一行的差分
}
for (int i = 1; i < rows; i++) {
diff[i][0] = matrix[i][0] - matrix[i - 1][0]; // 第一列的差分
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; j++) {
// 计算其他位置的差分
diff[i][j] = matrix[i][j] - matrix[i - 1][j] - matrix[i][j - 1] + matrix[i - 1][j - 1];
}
}
}
/**
* 更新范围 [(x1, y1), (x2, y2)] 内的所有元素,增加值 val。
* @param x1 起始行索引
* @param y1 起始列索引
* @param x2 结束行索引
* @param y2 结束列索引
* @param val 增加的值
*/
public void updateRange(int x1, int y1, int x2, int y2, int val) {
if (x1 < 0 || y1 < 0 || x2 >= rows || y2 >= cols) {
throw new IllegalArgumentException("更新范围超出矩阵边界");
}
diff[x1][y1] += val; // 左上角增加 val
if (y2 + 1 < cols) diff[x1][y2 + 1] -= val; // 右边界的下一列减少 val
if (x2 + 1 < rows) diff[x2 + 1][y1] -= val; // 下边界的下一行减少 val
if (x2 + 1 < rows && y2 + 1 < cols) diff[x2 + 1][y2 + 1] += val; // 右下角增加 val
}
/**
* 根据差分数组还原原始矩阵。
* @return 还原后的二维数组
*/
public int[][] getResult() {
int[][] res = new int[rows][cols];
res[0][0] = diff[0][0]; // 左上角元素直接赋值
for (int j = 1; j < cols; j++) {
res[0][j] = res[0][j - 1] + diff[0][j]; // 第一行累加还原
}
for (int i = 1; i < rows; i++) {
res[i][0] = res[i - 1][0] + diff[i][0]; // 第一列累加还原
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; j++) {
// 其他位置累加还原
res[i][j] = res[i - 1][j] + res[i][j - 1] - res[i - 1][j - 1] + diff[i][j];
}
}
return res;
}
/**
* 打印差分数组(用于调试)。
*/
public void printDiffArray() {
for (int[] row : diff) {
System.out.println(Arrays.toString(row));
}
}
}
样例
import java.util.Arrays;
// 一维差分数组类
class DiffArray {
public int[] diff; // 差分数组
/**
* 构造函数:通过传入的数组初始化差分数组。
* @param nums 原始数组
*/
public DiffArray(int[] nums) {
int n = nums.length;
diff = new int[n + 1]; // 长度为 n+1,方便边界处理
diff[0] = nums[0]; // 第一个元素直接赋值
for (int i = 1; i < n; i++) {
diff[i] = nums[i] - nums[i - 1]; // 计算差分
}
}
/**
* 更新范围 [l, r] 内的所有元素,增加值 val。
* @param l 起始索引
* @param r 结束索引
* @param val 增加的值
*/
public void updateRange(int l, int r, int val) {
diff[l] += val; // 左边界增加 val
if (r + 1 < diff.length) {
diff[r + 1] -= val; // 右边界后一位减少 val
}
}
/**
* 根据差分数组还原原始数组。
* @return 还原后的数组
*/
public int[] getResult() {
int[] res = new int[diff.length - 1];
res[0] = diff[0]; // 第一个元素直接赋值
for (int i = 1; i < res.length; i++) {
res[i] = res[i - 1] + diff[i]; // 累加还原
}
return res;
}
}
// 二维差分数组类
class DiffArray2D {
private int[][] diff; // 差分数组
private int rows, cols; // 原始矩阵的行数和列数
/**
* 构造函数:通过传入的 matrix 初始化差分数组。
* @param matrix 原始二维数组
*/
public DiffArray2D(int[][] matrix) {
this.rows = matrix.length;
this.cols = matrix[0].length;
this.diff = new int[rows][cols];
// 初始化差分数组
diff[0][0] = matrix[0][0]; // 左上角元素直接赋值
for (int j = 1; j < cols; j++) {
diff[0][j] = matrix[0][j] - matrix[0][j - 1]; // 第一行的差分
}
for (int i = 1; i < rows; i++) {
diff[i][0] = matrix[i][0] - matrix[i - 1][0]; // 第一列的差分
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; j++) {
// 计算其他位置的差分
diff[i][j] = matrix[i][j] - matrix[i - 1][j] - matrix[i][j - 1] + matrix[i - 1][j - 1];
}
}
}
/**
* 更新范围 [(x1, y1), (x2, y2)] 内的所有元素,增加值 val。
* @param x1 起始行索引
* @param y1 起始列索引
* @param x2 结束行索引
* @param y2 结束列索引
* @param val 增加的值
*/
public void updateRange(int x1, int y1, int x2, int y2, int val) {
if (x1 < 0 || y1 < 0 || x2 >= rows || y2 >= cols) {
throw new IllegalArgumentException("更新范围超出矩阵边界");
}
diff[x1][y1] += val; // 左上角增加 val
if (y2 + 1 < cols) diff[x1][y2 + 1] -= val; // 右边界的下一列减少 val
if (x2 + 1 < rows) diff[x2 + 1][y1] -= val; // 下边界的下一行减少 val
if (x2 + 1 < rows && y2 + 1 < cols) diff[x2 + 1][y2 + 1] += val; // 右下角增加 val
}
/**
* 根据差分数组还原原始矩阵。
* @return 还原后的二维数组
*/
public int[][] getResult() {
int[][] res = new int[rows][cols];
res[0][0] = diff[0][0]; // 左上角元素直接赋值
for (int j = 1; j < cols; j++) {
res[0][j] = res[0][j - 1] + diff[0][j]; // 第一行累加还原
}
for (int i = 1; i < rows; i++) {
res[i][0] = res[i - 1][0] + diff[i][0]; // 第一列累加还原
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; j++) {
// 其他位置累加还原
res[i][j] = res[i - 1][j] + res[i][j - 1] - res[i - 1][j - 1] + diff[i][j];
}
}
return res;
}
/**
* 打印差分数组(用于调试)。
*/
public void printDiffArray() {
for (int[] row : diff) {
System.out.println(Arrays.toString(row));
}
}
}
public class Main {
public static void main(String[] args) {
// 示例:一维差分数组
int[] nums = new int[]{1, 2, 3, 4, 5};
DiffArray diffArray1 = new DiffArray(nums);
System.out.println("初始化差分数组:");
System.out.println(Arrays.toString(diffArray1.diff));
// 更新范围 [1, 3] 内的所有元素,减去 1
diffArray1.updateRange(1, 3, -1);
// 打印更新后的差分数组并还原
System.out.println("更新后的差分数组:");
System.out.println(Arrays.toString(diffArray1.getResult()));
// 示例:二维差分数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 初始化二维差分数组
DiffArray2D diffArray2 = new DiffArray2D(matrix);
// 打印初始差分数组
System.out.println("初始差分数组:");
diffArray2.printDiffArray();
// 更新范围 [(1, 0), (2, 1)] 内的所有元素,增加 1
diffArray2.updateRange(1, 0, 2, 1, 1);
// 打印更新后的差分数组
System.out.println("更新后的差分数组:");
diffArray2.printDiffArray();
// 还原原始矩阵并打印
int[][] originalMatrix = diffArray2.getResult();
System.out.println("还原后的原始矩阵:");
for (int[] row : originalMatrix) {
System.out.println(Arrays.toString(row));
}
}
}
四、优化与同类算法对比
算法对比/复杂度分析
| 场景 | 前缀和 | 暴力法 | 差分 |
|---|---|---|---|
| 区间和查询 | O(1)(预处理 O(n)) |
O(n) |
不适用 |
| 区间加减操作 | 不适用 | O(k) |
O(1)(预处理 O(1)) |
| 空间复杂度 | O(n) 或 O(mn) |
O(1) |
O(n) 或 O(mn) |
五、相关题目推荐
前缀和经典题
- LeetCode 303. 区域和检索 - 数组不可变
- 560. 和为K的子数组
- LeetCode 525. 连续数组(0/1转换为前缀和问题)
- LeetCode 304. 二维区域和检索 - 矩阵不可变
- 1292. 元素和小于等于阈值的正方形
- 蓝桥杯例题:最大子段和、求和问题等。
差分经典题
- LeetCode 370. 区域和检索 - 数组可修改(结合差分的变种)
- LeetCode 1109. 航班预订统计(区间加法问题)
- 二维差分:2536. 子矩阵元素加1
- 蓝桥杯例题:区间更新后的最终数组计算。

浙公网安备 33010602011771号