前缀和模板

前缀和

1.一维数组前缀和

以力扣第303道题为例。题目描述如下:给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点

正常思路是遍历数组,将nums[i]到nums[j]的数字相加,时间复杂度为O(n)。那么有没有办法将时间复杂度降为O(1)呢?答案是肯定的,就是前缀和所谓前缀和,就是 nums[i]之前全部元素的和。 用一个数组(preSum)存储前缀和,那么这题就变成了preSum[j]-preSum[i]。接下来就用代码实现前缀和。

//前缀和的实现
int n = nums.length; 
int[] preSum = new int[nums.length+1];
for (int i=0;i<n;i++){
    preSum[i+1] = preSum[i]+nums[i]; 
}

2.二维数组前缀和

一维数组较为简单,那么看一下二维数组的。以力扣304道题为例。题目描述如下:

给定一个二维矩阵 matrix,以下类型的多个请求:
    计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
    
    NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
    
    int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、
    右下角 (row2, col2) 所描述的子矩阵的元素总和。

这题同样可以使用一维前缀和来实现。定义一个数组sums[m][n+1],统计每一行的前缀和。实现如下:

int[][] sums;
public NumMatrix(int[][] matrix) {
    int m = matrix.length;
    if (m > 0) {
        int n = matrix[0].length;
        //列设为n+1,是为了方便处理0这个特殊情况
        sums = new int[m][n + 1];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //统计matrix[i][j+1]的前缀和
                sums[i][j + 1] = sums[i][j] + matrix[i][j];
            }
        }
    }
}

public int sumRegion(int row1, int col1, int row2, int col2) {
    int sum = 0;
    //遍历每一行,相加
    for (int i = row1; i <= row2; i++) {
        //这里是col2+1 是因为要求col2的前缀和
        sum += sums[i][col2 + 1] - sums[i][col1];
    }
    return sum;
}

这道题同样可以使用二维前缀和来实现。二维数组是一维数组的延伸。

设二维数组 A 的大小为 m * n,行下标的范围为 [1, m],列下标的范围为 [1, n]。

数组 P 是 A 的前缀和数组,等价于 P 中的每个元素 P[i] [j]:

  • 如果 i 和 j 均大于 0,那么 P[i] [j] 表示 A 中以 (1, 1) 为左上角,(i, j) 为右下角的矩形区域的元素之和;
  • 如果 i 和 j 中至少有一个等于 0,那么 P[i] [j] 也等于 0。

数组 P 可以帮助我们在 O(1) 的时间内求出任意一个矩形区域的元素之和。具体地,设我们需要求和的矩形区域的左上角为 (x1, y1),右下角为 (x2, y2),则该矩形区域的元素之和可以表示为:

sum = A[x1..x2] [y1..y2] = P[x2][y2] - P[x1 - 1][y2] - P[x2][y1 - 1] + P[x1 - 1][y1 - 1]

以下图为例,当 A 的大小为 8 * 5,需要求和的矩形区域(深绿色部分)的左上角为 (3, 2),右下角为 (5, 5) 时,该矩形区域的元素之和为 P[5] [5] - P[2] [5] - P[5] [1] + P[2] [1]。

https://gitee.com/MayBeBa/images/raw/master/image-20211118191454895.png

那么如何得到数组 P 呢?我们按照行优先的顺序依次计算数组 P 中的每个元素,即当我们在计算 P[i] [j] 时,数组 P 的前 i - 1 行,以及第 i 行的前 j - 1 个元素都已经计算完成。此时我们可以考虑 (i, j) 这个 1 * 1 的矩形区域,根据上面的等式,有:

A[i][j] = P[i][j] - P[i-1][j] - P[i][j-1] + P[i-1][j-1]

由于等式中的 A[i] [j],P[i - 1] [j],P[i] [j - 1] 和 P[i - 1] [j - 1] 均已知,我们可以通过:

P[i][j] = P[i-1][j] + P[i][j-1] - P[i-1][j-1] + A[i][j]

在 O(1)的时间计算出 P[i] [j]。在此之后,我们就可以很方便地在 O(1) 的时间内求出任意一个矩形区域的元素之和了。代码实现如下:

int[][] dp;
public NumMatrix(int[][] matrix) {
    int m = matrix.length;
    int n = matrix[0].length;
    dp = new int[m+1][n+1];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n ; j++) {
            //求前缀和
            dp[i+1][j+1] = dp[i][j+1]+dp[i+1][j]+matrix[i][j]-dp[i][j];
        }
    }
}
public int sumRegion(int row1, int col1, int row2, int col2) {
    return dp[row2+1][col2+1]+dp[row1][col1] - dp[row1][col2+1] - dp[row2+1][col1];
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值