前缀和
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]。
那么如何得到数组 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];
}