前缀和

前缀和思想在算法中的应用

一维前缀和

题目描述

用于计算一维数组的区间和,将时间复杂度从$O(n * m) ,m 是查询的次数$简化到$O(n)$

算法思想

前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。

例如,统计 vec[i] 这个数组上的区间和。

  • 先做累加,即 p[i] 表示 下标 0 到 i 的 vec[i] 累加 之和。
  • 统计vec数组上 下标 i 到下标 j 之间的累加和时使用 p[j]-p[i-1] 即可
1
2
3
p[i] = vec[0] + vec[1] + ... vec[i];
p[j] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5] + ..vec[j];
p[j] - p[i] = vec[i+1] + vec[i+2] + vec[i+3] + ... +vec[j];

img

代码模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 求解前缀和数组
int presum = 0;
for (int i = 0; i < n; i++) {
    presum += vec[i];
    p[i] = presum;
}
// 求解子区间和
int sum;
if (a == 0) {
     sum = p[b];
} else {
     sum = p[b] - p[a - 1];
 }

经典例题

板子题

题目描述

题目链接(opens new window)

给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

输入描述

第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。

输出描述

输出每个指定区间内元素的总和。

输入示例

1
2
3
4
5
6
7
8
5
1
2
3
4
5
0 1
1 3

输出示例

1
2
3
9

数据范围:

0 < n <= 100000

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = scanner.nextInt();
        int[] vec = new int[n];
        int[] p = new int[n];

        int presum = 0;
        for (int i = 0; i < n; i++) {
            vec[i] = scanner.nextInt();
            presum += vec[i];
            p[i] = presum;
        }

        while (scanner.hasNextInt()) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();

            int sum;
            if (a == 0) {
                sum = p[b];
            } else {
                sum = p[b] - p[a - 1];
            }
            System.out.println(sum);
        }

        scanner.close();
    }
}

leetcode 560 和为K的子数组

题目描述

题目链接

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数

子数组是数组中元素的连续非空序列。

示例 1:

1
2
输入:nums = [1,1,1], k = 2
输出:2

示例 2:

1
2
输入:nums = [1,2,3], k = 3
输出:2

思路解析

  • 先构造出nums的前缀和数组
  • 根据公式,从区间[i,j]的区间和为p[j]-p[i-1]=k
  • 注意要先在map中放入(0,1),因为当j=0时只有1种可能
  • 因此可以遍历前缀和数组,相当于求解p[j]-k在map中出现多少次

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
    public int subarraySum(int[] nums, int k) {
        HashMap<Integer,Integer>map=new HashMap<>();
        int[]preSum=new int[nums.length];
        int pre=0;
        int count=0;
        for(int i=0;i<nums.length;i++){
            pre+=nums[i];
            preSum[i]=pre;
        }
        map.put(0,1);
        // 区间[i,j]的区间和为preSum[j]-preSum[i-1]=k
        for(int j=0;j<nums.length;j++){
            int temp=preSum[j]-k;
            if(map.containsKey(temp)){
                count+=map.get(temp);
            }
            map.put(preSum[j],map.getOrDefault(preSum[j],0)+1);
        }
        return count;
    }
}
그 경기 끝나고 좀 멍하기 있었는데 여러분 이제 살면서 여러가
使用 Hugo 构建
主题 StackJimmy 设计