一、简述
什么是前缀和?
所谓前缀和,是指一个数组中的前i项的和。设有数组a[0...n],那么s[i] = a[0] + ... + a[i]。这样s数组就是a数组的前缀和数组。
通常,在算法题中,前缀和计算时间复杂度为O(n),后续要查询对应的操作算法复杂度为O(1),使用前缀和后,能大大减少算法复杂度。
二、遇见的题型分类
2.1 区间加/减法
案例:若题目给出了一系列操作,要求将给出的数组范围里进行加、减操作,则可以使用该方法,如下例题。
例题源头: https://leetcode.cn/problems/range-addition/
例题题图示:
核心点
- 若要求[start, end]区间加上value,则只需要将初始化的后的数组(初始化的值为0),nums[start] +=value,nums[end + 1] -= value
- 将数组累加,即:初始化且进行操作过后的数组进行前缀和计算后,会得出最后需要的结果数组。
解释:
- nums[start] +=value是为了让后续累加时,数组后面的值都会加上value
- nums[end + 1] -= value是为了让超出end的之后的数组不要加上该值,反过来想,减去该值就可以
代码:
public int[] getModifiedArray(int length, int[][] updates) {
int result[] = new int[length];
// for (int i = 0; i < updates.length; i++) {
// int j = updates[i][0], k = updates[i][1], value = updates[i][2];
// while(j <= k) {
// result[j] += value;
// j++;
// }
// }
// 前缀和算法
for(int i = 0; i < updates.length; i++) {
int start = updates[i][0], end = updates[i][1], value = updates[i][2];
result[start] += value;
if (end + 1 < length) {
result[end + 1] -= value;
}
// System.out.println(Arrays.toString(result));
}
for(int i = 1; i < length; i++) {
result[i] += result[i - 1];
}
// System.out.println("----");
// System.out.println(Arrays.toString(result));
return result;
}
2.2 前后缀协同使用
解释:前缀和为前往后加,后缀和则为后往前加,思路是类似的。有些题目可能要前后缀一起使用,结果为获取相应的前缀和后缀之间的关系得出的结果。
例题题图示:
核心思路:
1.假如我们在i的时候关门,代价为i前的N的个数+i后的Y的个数
2.计算 N的前缀和prefix 以及 Y的后缀和suffix
3.第i时关门的代价=prefix[i] + suffix[i + 1]
代码:
public int bestClosingTime(String customers) {
int length = customers.length(), result = 0;
int suffix1[] = new int[length], prefix0[] = new int[length];
for (int i = 0; i < length; i++) {
if (customers.codePointAt(i) == 'N') {
if (i == 0) {
prefix0[i] = 1;
} else {
prefix0[i] = prefix0[i - 1] + 1;
}
} else {
if (i != 0) {
prefix0[i] = prefix0[i - 1];
}
}
int sufIndex = length - 1 - i;
if (customers.codePointAt(sufIndex) == 'Y') {
if (i == 0) {
suffix1[sufIndex] = 1;
} else {
suffix1[sufIndex] = suffix1[sufIndex + 1] + 1;
}
} else {
if(i != 0) {
suffix1[sufIndex] = suffix1[sufIndex + 1];
}
}
}
// System.out.println(Arrays.toString(prefix0));
// System.out.println(Arrays.toString(suffix1));
int cost = suffix1[0];
for (int i = 0; i < length - 1; i++) {
if (cost > prefix0[i] + suffix1[i + 1]) {
cost = prefix0[i] + suffix1[i + 1];
result = i + 1;
}
}
if (prefix0[length - 1] < cost) {
result = length;
}
return result;
}
2.3 二维前缀和
2.3.1 计算
二维前缀和的计算,可以按行或按列进行累加计算。这里以行为例,按照每行,进行一维数组的累加前缀和就可以了。对原数组matrix[0...n][0...m] 而言,前缀和数组prifix[i][j] 表示的是第i行的前j个数的和。
这样,我们就能够通过前缀和计算子数组范围内的值了。
2.3.2 例题
例题源头: leetcode.cn/problems/O4…
例题题图示:
核心点
若要求[row1, col1, row2, col2]范围内的值
1.我们只需要从row1到row2遍历
2.值result += prefix[i][col2] - prefix[i][col1]即可,也就是加上每行对应子数组内容的值
代码:
class NumMatrix {
public int prefixRow[][];
public NumMatrix(int[][] matrix) {
this.prefixRow = new int[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (j == 0) {
this.prefixRow[i][j] = matrix[i][j];
} else {
this.prefixRow[i][j] = this.prefixRow[i][j - 1] + matrix[i][j];
}
}
System.out.println(Arrays.toString(this.prefixRow[i]));
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
int result = 0;
if (col1 == 0) {
for (int i = row1; i <= row2; i++) {
result += this.prefixRow[i][col2];
}
} else {
for (int i = row1; i <= row2; i++) {
result += this.prefixRow[i][col2] - this.prefixRow[i][col1 - 1];
}
}
return result;
}
}
2.4 异或相关内容
2.4.1 计算
异或的计算也满足前缀和的效果,若要求数组i~j的异或结果,我们可以逐步推出如下等式( ^为异或符)
- a[1] ^ a[2] ^ ...... ^ a[i] ^ a[i + 1] ^ ...... ^ a[j] ,若将其用前缀和表示我们用prefix[j] 表示该式子
- 那么 prefix[i - 1] = a[1] ^ a[2] ^ ...... ^ a[i - 1]
- 得出prefix[j] ^ prefix[i - 1] = (a[1] ^ a[2] ^ ...... ^ a[i] ^ a[i + 1] ^ ...... ^ a[j]) ^ ( a[1] ^ a[2] ^ ...... ^ a[i - 1] )
- 由3的式子得出,prefix[j] ^ prefix[i] = a[i] ^ ...... ^ a[j],显然和前缀和的效果一致,即:性质与前缀和类似
2.4.2 例题
例题源头: leetcode.cn/problems/xo…
例题题图示:
代码:
public int[] xorQueries(int[] arr, int[][] queries) {
int length = arr.length, result[] = new int[queries.length];
if (length == 1) {
for (int i = 0; i < queries.length; i++) {
result[i] = arr[0];
}
return result;
}
for (int i = 1; i < length; i++) {
arr[i] ^= arr[i - 1];
}
for (int i = 0; i < queries.length; i++) {
int start = queries[i][0], end = queries[i][1];
if (start == 0) {
result[i] = arr[end];
} else {
result[i] = arr[end] ^ arr[start - 1];
}
}
// System.out.println(Arrays.toString(result));
return result;
}
2.5 差分数组
2.5.1 差分数组的定义和性质
计算方式如下:
设有数组a = { 3, 8, 6, 4, 9, 2, 5, 7, 1 },那么a的差分数组的计算方式为,为后一项减前一项的值,第一项不动,即a的差分数组b:
b = { 3, (8 - 3), (6 - 8), (4 - 6), (9 - 4), (2 - 9), (5 - 2), (7 - 5), (1 - 7)}
b = { 3, 5, -2, -2, 5, -7, 3, 2, -6 }
那么对b进行累加,我们会发现:c = { 3, 8, 6, 4, 9, 2, 5, 7, 1 },累加后的数组为a
这也就是差分数组的 性质 :差分数组的前缀和等于原数组。
2.5.2 例题
目前遇到的题目掺杂了其他的内容,不适合当例题,后续遇到合适的例题再补充。