一、应用场景?
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和.prefix[i+1] 就代表着 nums[0…i] 所有元素的累加和, 如果我们想求区间 nums[i…j] 的累加和, 只要 计算 prefix[j+1] - prefix[i] 即可, ⽽不需要遍历整个区间求和。
二、模板
preSum = new int[nums.length + 1];
//preSum[i]数组维护nums前i个和,即[0,...,i-1],preSum[0]=0(方便计算);
for (int i = 0; i < nums.length; i++) { //e.g. nums[0,1,2,3]
preSum[i + 1] = preSum[i] + nums[i]; // preSum[0,0,1,3,6]
System.out.println(preSum[i]);
}
三、实践
303. 区域和检索 - 数组不可变
public class NumArray {
private int[] preSUm;
public NumArray(int[] nums) {
fun(nums);
}
//构造前缀和数组
public void fun(int[] nums) {
preSUm = new int[nums.length + 1];//preSum[i]代表了nums中前i个数之和,即nums[0...i-1]的和
//preSum[0]=0为了方便后续计算
for (int i = 1; i <= nums.length; i++) {
preSUm[i] = preSUm[i - 1] + nums[i - 1];
}
}
public int sumRange(int left, int right) {
return preSUm[right + 1] - preSUm[left];
}
}
304. 二维区域和检索 - 矩阵不可变
计算红⾊的这个⼦矩阵的元素之和, 可以⽤绿⾊矩阵减去蓝⾊矩阵减去橙⾊矩阵最后加上粉⾊矩 阵, ⽽绿蓝橙粉这四个矩阵有⼀个共同的特点, 就是左上⻆就是 (0, 0) 原点。
可以维护⼀个⼆维 preSum 数组, 专⻔记录以原点为顶点的矩阵的元素之和, 就可以⽤⼏次加减运 算算出任何⼀个⼦矩阵的元素和:
public class NumMatrix {
private int[][] preSum;
public NumMatrix(int[][] matrix) {
fun(matrix);
}
//构造前缀和
public void fun(int[][] matrix) {
int n = matrix.length, m = matrix[0].length;
preSum = new int[n + 1][m + 1];//preSum[i][j]代表matrix[0...i-1][0...j-1]之和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return preSum[row2 + 1][col2 + 1] - preSum[row1][col2 + 1] - preSum[row2 + 1][col1] + preSum[row1][col1];
}
}
560. 和为 K 的子数组
//方法一
public int subarraySum(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] preSum = new int[nums.length + 1];//1、构造前缀和
for (int i = 1; i <= nums.length; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
}
int res = 0;
for (int i = 1; i <= nums.length; i++) {//2、穷举所有元素
for (int j = 0; j < i; j++) {
if (preSum[i] - preSum[j] == k) {//3、nums[j,...,i-1]的和
res++;
}
}
}
return res;
}
方法2(对方法一的优化)
public int subarraySum(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return 0;
}
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int res = 0, preSum = 0;
for (int i = 0; i < nums.length; i++) {
preSum += nums[i];//记录前缀和
if (map.containsKey(preSum - k)) {//有满足条件的前缀和
res += map.get(preSum - k);
}
map.put(preSum, map.getOrDefault(preSum, 0) + 1);//更新满足提议的前缀和个数
}
return res;
}
总结
前缀和数组主要适⽤的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。