1.一维数组前缀和
一维数组的前缀和,就是这个数组从第m项到第n项的和
,(从1开始,默认索引为0的位置的值为0), 一般我们计算都是使用一个for循环,显然这样每次计算都会花费O(n)的时间复杂度.我们可以使用空间换时间,使用一个额外的数组,记录原数组中从第一项到每一项的和,然后在计算原数组第m到第n项和
时只需要将1到n项
的和减去1到(m-1)
项的和即可,
原数列A A1 A2 A3 ...An
使用Sn表示数列A的前n项的和 即
S1 = A1
S2 = A1+A2
S3 = A1+A2+A3
Sn-1 = A1+A2+A3+...An-1
Sn = A1+A2+A3+...+An-1+An = Sn-1 + An
所以可以推出:
Sn = Sn-1 +An ①
---------------------------------------------------------------------------
我们现在要求A数列从 Am 到An的和
原始做法就是 Am+Am+1+...+An-1+An
我们引入了数列S后,计算Am到An的和 由于n大于等于m
Sm-1 = A1 + A2 +...+ Am-1
Sn = A1 + A2 + ... +Am-1+Am+...+ An-1 +An
所以可以推出 Am到An的和 就等于 Sn - Sm-1 ②
例题
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n+1];
//s数组记录的就是数组nums中从第一项到第n项的和
int[] s = new int[n+1];
int m = scanner.nextInt();
//从索引为1开始输入,0的位置默认是0
for (int i = 1; i <= n ; i++) {
nums[i] = scanner.nextInt();
}
//公式一 Sn = Sn-1 +An 求前缀和
for (int j = 1; j <= n; j++) {
s[j] = s[j - 1] + nums[j];
}
while (m-- > 0) {
int l = scanner.nextInt();
int r = scanner.nextInt();
//公式二 l到r的和等于 Sr - Sl-1
int sum = s[r] - s[l - 1];
System.out.println(sum);
}
}
}
2.二维数组前缀和
一个点A(i,j)(从1开始)的前缀和记为S(i,j),是以坐标为(i,j)的点坐标的左上角的区域的所有元素和
有下面的示意图可以得到
某一个点的前缀和
S(i,j)= = S(i-1,j) + S(i,j-1) - S(i-1,j-1) + A(i,j)
-----------------------------------------------------------------------------
点(x1,y1)到点(x2,y2)之间的前缀和,就是以(x1,y1)为左上角以(x2,y2)为右下角的矩阵的前缀和
公式
Sum = S(x2,y2) - S(x1-1,y2) - S(x2,y1-1) + S(x1-1,y1-1)
示意图
例题
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int q = scanner.nextInt();
int[][] A = new int[n + 1][m + 1];
int[][] S = new int[n + 1][m + 1];
//从索引为1开始赋值
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
A[i][j] = scanner.nextInt();
}
}
//计算每个位置的前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
}
}
while (q-- > 0) {
int x1 = scanner.nextInt();
int y1 = scanner.nextInt();
int x2 = scanner.nextInt();
int y2 = scanner.nextInt();
//公式 计算某两个点的前缀和
int sum = S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1];
System.out.println(sum);
}
}
}
3.一维数组差分
差分是前缀和的逆运算
,比如说有一个数组A,它的前缀和数组是S,即S是A的前缀和数组,那么A就是S的差分数组
给定一个数组S如何求他的差分数组A
根据第一节的前缀和的推导,以及下面的推导,可以得到An = Sn - Sn-1
....
Sn-1 = Sn-2 + An-1
Sn = Sn-1 + An
所以我们现在要根据Sn求出An 由上面的式子可以得到,An = Sn - Sn-1
具体题目
给定一个数组,对于数组的某一段区间,都加上一个数,(操作m次)返回处理后的结果,
思路
- 我们可以使用直接扫描的做法,扫描这个区间,给区间的数都加上指定的值,
- 也可以使用差分的做法,先求出这个数组的差分数组,然后将差分数组的区间(比如从a到b区间)索引a上加上指定数,b+1索引上减去指定数,最后求出这个差分数组的前缀和数组就是答案
为什么这样做可以?
比如我们有一个数组是S
S1 = S0 + A1
S2 = S1 + A2
S3 = S2 + A3
...
Sl = Sl-1 + Al
....
Sr = Sr-1 + Ar
...
Sn-1 = Sn-2 + An-1
Sn = Sn-1 + An
-------------------------
它的差分数组A为
A1 = S1 - S0
A2 = S2 - S1
。。。。
Al = Sl - Sl-1
...
Ar = Sr - Sr-1
An-1 = Sn-1 - Sn-2
An = Sn - Sn-1
------------------------
所以,我们现在要将S数组中,从l位置到r位置上的是都加上C,那么我们先求出S的差分数组A,
我们将Al位置上的数+C,然后求出它的前缀和数组
A1 = S1 - S0;
...
Al = Sl - Sl-1 + C
...
An = Sn - Sn-1
||
||
前缀和数组
S1 = S0 + A1;
...
Sl = Sl-1 + Al = Sl + C
Sl+1 = Sl + Al+1 =Sl+1 + C
....
Sn = Sn + C
所以我们发现,当Al+C后,它的前缀和数组从l到n都会加上C,但是我们现在只想让前缀和从l到r位置上的数加上C,根据上面的实验,我们A数组从r+1位置上的数全都减去C,那么S从r+1位置到n会减去C,所以我们就完成了前缀和数组的l到r位置上的数+C,其他位置的数不变的操作
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] S = new int[n + 2];
int[] A = new int[n + 2];
// 原数组
for (int i = 1; i <= n; i++) {
S[i] = scanner.nextInt();
}
//先求他的差分数组
for (int j = 1; j <= n; j++) {
A[j] = S[j] - S[j - 1];
}
while (m-- > 0) {
int l = scanner.nextInt();
int r = scanner.nextInt();
int c = scanner.nextInt();
//差分数组的l位置+c
A[l] += c;
//差分数组的r位置-c
A[r + 1] -= c;
}
//操作完成后求出查分数组的前缀和数组,这样就完成了原数组的l到r位置的操作
for (int i = 1; i <= n; i++) {
S[i] = S[i - 1] + A[i];
}
//输出
for (int i = 1; i <= n; i++) {
System.out.print(S[i] + " ");
}
}
}
4.二维数组差分
二维数组的差分跟一维数组类似,都是前缀和的逆运算。根据求点(i,j)
前缀和的公式
S(i,j)= = S(i-1,j) + S(i,j-1) - S(i-1,j-1) + A(i,j)
可以得到一个数组的某个点经过差分后运算后的结果
A(i,j) = S(i,j) - S(i-1,j) - S(i,j-1) + S(i-1,j-1)
核心公式
求出差分数组后对(x1,y1)(x2,y2)的操作就是下面的四步
例题
跟上面的差分那道题类似,将二维数组的某一个子矩阵都加上某个数C,这里也可以先将原数组化为差分矩阵,然后可以得出差分矩阵的某个点加上或者减去C对于它的前缀和数组的影响,即可解决。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] S = new int[n + 2][m + 2];
int[][] A = new int[n + 2][m + 2];
int q = scanner.nextInt();
//原数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
S[i][j] = scanner.nextInt();
}
}
//求其差分数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
A[i][j] = S[i][j] - S[i - 1][j] - S[i][j - 1] + S[i - 1][j - 1];
}
}
//公式 修改差分数组指定位置的元素
while (q-- > 0) {
int x1 = scanner.nextInt();
int y1 = scanner.nextInt();
int x2 = scanner.nextInt();
int y2 = scanner.nextInt();
int C = scanner.nextInt();
//核心就是这里 改变差分数组的一些值
A[x1][y1] += C;
A[x1][y2 + 1] -= C;
A[x2 + 1][y1] -= C;
A[x2 + 1][y2 + 1] += C;
}
//构造前缀和数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
System.out.print(S[i][j]+ " ");
}
System.out.println();
}
}
}