赞
踩
在求数组或者矩阵求和等问题,我们如果采用暴力解法,时间复杂度可能会达到O(n)或者更高,因此,我们可利用前缀和来解决。
前缀和是指序列中的n项和,相当于数学问题中秋数列的前n项和。主要用于数组或列表中求解子数组的和、查询区间和等问题,能够将时间复杂度从O(n)降低到O(1)。
接下来我们就通过题目来更好的来理解前缀和。
本道题就是在长度为n的数组中根据输入的区间下标[L,R]来计算区间和。如果我们使用暴力解法,时间复杂度为O(n),但是我们有更优的解法,使用前缀和,能使查询区间和的复杂度降为O(1).
- import java.util.Scanner;
-
- // 注意类名必须为 Main, 不要有任何 package xxx 信息
- public class Main {
- public static void main(String[] args) {
- Scanner in = new Scanner(System.in);
- int n = in.nextInt();
- int q = in.nextInt();
- long[] a = new long[n + 1];
- long[] dp = new long[n + 1];
- a[0] = dp[0] = 0;
- for (int i = 1; i <= n; i++) {
- a[i] = in.nextLong();
- }
- //处理前缀和数组
- for (int i = 1; i <= n; i++) {
- dp[i] = dp[i - 1] + a[i];
- }
- //计算区间和
- while (q > 0) {
- int l = in.nextInt();
- int r = in.nextInt();
- long sum = dp[r] - dp[l - 1];
- System.out.println(sum);
- q--;
- }
- }
- }

时间复杂度为O(n+q),n是数组长度,q是查询次数。
空间复杂度为为O(n),需要借助dp数组。
以
8 1
3 4 5 8 2 3 4 5
4 7为例
第一步:初始化 、并预处理a数组
根据输入的值:n=8,q=1 a[9]={0,3,4,5,8,2,3,4,5}
第二步:处理前缀和数组
dp[9]={0,3,7,12,20,22,25,29,35}
第三步:根据L和R计算前缀和
在输入中,我们可以得到L=4,R=7
所以前缀和sum=dp[7]-d[3]=29-12=17
我们可以测试一下,可以看到,结果确实与我们算的一致。
根据这道题目,我们可以总结出计算前缀和时的模板:
本道题是要在一个矩阵中求子矩阵的和,如果我们使用暴力解法,时间复杂度为O(n*m*q),会超时,但对于这种求子矩阵和的问题,我们可以使用前缀和来解决。
在说算法步骤之前,先来补充个知识,我们在计算一个矩阵的大小时,其实是可以将分为几部分来求解的,对于本道题的矩阵,我们可以分解为:
dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1]
即S=(A+B)+(A+C)+D-A
3.计算子矩阵和:当我们处理完前缀和矩阵之后,就可以利用矩阵来进行求和。
根据所给的坐标(X1,Y1),(X2,Y2),我们可以得到以下:
同理的,我们根据坐标,将矩阵分为4部分,其中D矩阵就是我们所要求的子矩阵。我们想要求D的话,根据
D=(A+B+C+D)-(A+B)-(A+C)+A
=dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]
即可得到。
- import java.util.Scanner;
-
- // 注意类名必须为 Main, 不要有任何 package xxx 信息
- public class Main {
- public static void main(String[] args) {
- Scanner sc=new Scanner(System.in);
- int n=sc.nextInt();
- int m=sc.nextInt();
- int q=sc.nextInt();
- long[][] a=new long[n+1][m+1];
- long[][] dp=new long[n+1][m+1];
- for(int i=1;i<=n;i++){
- for(int j=1;j<=m;j++){
- a[i][j]=sc.nextLong();
- }
- }
- //预处理矩阵
- for(int i=1;i<=n;i++){
- for(int j=1;j<=m;j++){
- //S=(A+B)+(A+C)+D-A
- dp[i][j]=dp[i-1][j]+dp[i][j-1]+a[i][j]-dp[i-1][j-1];
- }
- }
-
- //求子矩阵和
- while(q>0){
- int x1=sc.nextInt();
- int y1=sc.nextInt();
- int x2=sc.nextInt();
- int y2=sc.nextInt();
- //sum=(A+B+C+D)-(A+B)-(A+C)+A=D
- long sum=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
- System.out.println(sum);
- q--;
- }
- }
- }

时间复杂度为O(n*m)+O(q),n,m为二维数组的行,列,q为查询次数。
空间复杂度为O(n*m).需要开辟两个数组,a和dp。
以
3 4 3 1 2 3 4 3 2 1 0 1 5 7 8 1 1 2 2为例
第一步:初始化
根据输入的值,可以得到:
n=3,m=4,q=3
a数组如下
第二步:处理前缀和矩阵
根据二维数组a,我们可以计算出前缀和数组dp为
这道题是要我们在数组中,找以一个点为中心,左右区间和相等。这里我们可以利用前缀和来进行解答。这里我们需要利用到两个数组,yp和np。
- /**
- * 寻找数组的中心索引
- * 中心索引的左侧元素之和等于右侧元素之和
- *
- * @param nums 输入的整数数组
- * @return 返回中心索引,如果不存在,则返回-1
- */
- public int pivotIndex(int[] nums) {
- // 数组长度
- int n=nums.length;
- // yp数组用于存储从左侧累加的元素之和
- int[] yp=new int[n];
- // np数组用于存储从右侧累加的元素之和
- int[] np=new int[n];
-
- // 从左侧开始累加,计算每个位置左侧元素之和
- for(int i=1;i<n;i++){
- yp[i]=yp[i-1]+nums[i-1];
- }
-
- // 从右侧开始累加,计算每个位置右侧元素之和
- for(int i=n-2;i>=0;i--){
- np[i]=np[i+1]+nums[i+1];
- }
-
- // 遍历数组,寻找中心索引
- for(int i=0;i<n;i++){
- // 当找到左右两侧元素之和相等的位置时,返回该索引
- if(yp[i]==np[i]){
- return i;
- }
- }
- // 如果没有找到中心索引,返回-1
- return -1;
- }

算法示例:
以nums = [1, 7, 3, 6, 5, 6]为例
第一步:初始化
n=6
第二步:预处理
yp={0,1,8,11,16,21}
np={27,20,17,11,6,0}
第三步:判断
我们可以看到,yp[3]=np[3]=11,此时返回下标3即可。
本道题与上一道题类似,不过这道题是为了求除当前下标的数,其他数的乘积。那么这道题我们照样可以用前缀和。这里同样需要两个数组,前缀和数组yp,后缀和数组np。
- /**
- * 计算数组中每个元素的乘积,排除自身
- * 这个方法通过使用两个辅助数组来优化计算,避免了对每个元素重复遍历整个数组求乘积的过程
- *
- * @param nums 原始整数数组
- * @return 包含每个元素对应位置上的乘积结果的数组
- */
- public int[] productExceptSelf(int[] nums) {
- // 数组长度
- int n = nums.length;
- // yp数组用于存储每个位置左侧所有数字的乘积
- int[] yp = new int[n];
- // np数组用于存储每个位置右侧所有数字的乘积
- int[] np = new int[n];
-
- // 初始化左侧乘积数组,yp[0]为1,因为第一个元素左侧没有元素,乘积为1
- yp[0] = 1;
- for (int i = 1; i < n; i++) {
- yp[i] = yp[i - 1] * nums[i - 1];
- }
-
- // 初始化右侧乘积数组,np[n-1]为1,因为最后一个元素右侧没有元素,乘积为1
- np[n - 1] = 1;
- for (int i = n - 2; i >= 0; i--) {
- np[i] = np[i + 1] * nums[i + 1];
- }
-
- // 构建答案数组,每个元素是对应位置的左侧乘积和右侧乘积的乘积
- int[] ans = new int[n];
- for (int i = 0; i < n; i++) {
- ans[i] = yp[i] * np[i];
- }
-
- return ans;
- }

时间复杂度为O(n),n为数组长度。
空间复杂度为O(n)
以nums = [1,2,3,4]为例
第一步:初始化
n=4
第二步:处理缀合数组
根据yp[i]=yp[i-1]+nums[i-1]可得
yp={1,1,2,6}
根据np[i]=np[i+1]+nums[i+1]可得
np={24,12,4,1}
第三步:缀合数组相乘
ans={24,12,8,6}
第四步:返回结果
此时将ans数组返回即可。
前缀和上篇就先到这里了~
若有不足,欢迎指正~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。