2.前缀和与差分(一维和二维)

0、参考资料(讲解视频及博客等)

一、应用场景

前缀和算法

  1. 区间和查询
    • 例如:计算数组中任意区间 [l, r] 的和,时间复杂度从 O(n) 优化为 O(1)
  2. 子数组问题
    • 统计满足条件的子数组数量(如和为 K、可被 K 整除等)。
  3. 二维矩阵区域和
    • 快速计算矩阵中任意矩形区域的和,时间复杂度从 O(mn) 优化为 O(1)

差分算法

  1. 区间修改
    • 对数组的某个区间 [l, r] 进行加减操作,时间复杂度从 O(n) 优化为 O(1)
    • 例如:多次区间加减操作后的最终数组计算。
  2. 多场景应用
    • 适用于需要频繁修改区间值的场景,如温度变化、流量统计等。

二、核心思想/步骤

前缀和

一维

  1. 构建前缀和数组:s[i] = s[i-1] + a[i]
  2. 区间和计算:`sum[l..r] = s[r] - s[l-1]
    二维
  3. 构建前缀和数组:s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]
  4. 子矩阵和计算:`sum(x1,y1,x2,y2) = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]

差分

一维

  1. 构建差分数组:b[i] = a[i] - a[i-1](其中b[1]=a[1]
  2. 区间修改:b[l] += c; b[r+1] -= c
  3. 构建差分矩阵:b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
  4. 子矩阵修改: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)

五、相关题目推荐

前缀和经典题

  1. LeetCode 303. 区域和检索 - 数组不可变
  2. 560. 和为K的子数组
  3. LeetCode 525. 连续数组(0/1转换为前缀和问题)
  4. LeetCode 304. 二维区域和检索 - 矩阵不可变
  5. 1292. 元素和小于等于阈值的正方形
  6. 蓝桥杯例题:最大子段和、求和问题等。

差分经典题

  1. LeetCode 370. 区域和检索 - 数组可修改(结合差分的变种)
  2. LeetCode 1109. 航班预订统计(区间加法问题)
  3. 二维差分2536. 子矩阵元素加1
  4. 蓝桥杯例题:区间更新后的最终数组计算。
posted @ 2025-04-21 15:58  yuanyulinyi  阅读(55)  评论(0)    收藏  举报