前缀和preSum

本文介绍了前缀和的概念及其在LeetCode中的应用,包括求解数组区间和以及二维矩阵的前缀和。此外,还讨论了差分数组在处理数组区间元素增减操作时的优势。通过前缀和与差分数组,可以实现O(1)时间复杂度的区间和查询与数组修改操作。
摘要由CSDN通过智能技术生成

1.定义

一维数组:前缀和(Prefix Sum)的定义为:对于一个给定的数列 A, 它的前缀和数列 S 是通过递推能求出来得 S[i] = \sum_{j = 1}^{i}A[j] 部分和。

img

Leetcode上的一道题目:
**加粗样式
**

//写法一:
public int[] runningSum(int[] nums) {
        int[] preNum = new int[nums.length];
        for(int i = 0; i < nums.length;i++){
            if(i == 0){
                preNum[i] = nums[i];
            }else{
                preNum[i] = nums[i] + preNum[i - 1];
            }
        }
        return preNum;
    }

//写法二:
public int[] runningSum(int[] nums) {
        int[] preSum = new int[nums.length + 1];
        for(int i = 0; i < nums.length;i++){
            preSum[i + 1] = nums[i] + preSum[i];
        }
        return preSum;
    }

2.用途

用途一:求数组前i个数之和

求数组前i个数之和,是「前缀和」数组的定义,所以是最基本的用法。 举例而言:

  • 如果要求 nums 数组中的前 2 个数的和,直接返回preNum[2]即可。 针对写法二 ,写法一为(preNum[1])
  • 同理,如果要求 nums 数组中所有元素的和,直接返回 preSum[length](数组的长度)即可。

用途二:求数组的区间和

利用 preSum 数组,可以在 o(1)的时间内快速求出 nums 任意区间 [i,j]内的所有元素之和。

公式为:

sum(i,j) = preSum[j] - preSum[i - 1] (针对写法一)

sum(i,j) = preSum[j +1] - preSum[i] (针对写法二)

原理:其实就是消除公共部分即 0~i-1 部分的和,那么就能得到 i~j 部分的区间和。

注意写法二中,使用的是 preSum[j + 1]preSum[i],需要理解为什么这么做。

  • preSum[j + 1] 表示的是 nums 数组中 [0,j]的所有数字之和(包含0和 j)。
  • preSum[i]表示的是 nums数组中 [0,i - 1]的所有数字之和(包含0 和 i- 1)。
  • 当两者相减时,结果留下了 nums数组中 [i,j]的所有数字之和。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ed6DY46R-1651670372563)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20220504202049252.png)]

class NumArray {
    private int[] preSum;
    public NumArray(int[] nums) {
        int n = nums.length;
        preSum = new int[n + 1];
        for(int i = 0;i < n;i++){
            preSum[i + 1] = nums[i] + preSum[i];
        }
    }
    
    public int sumRange(int left, int right) {
        return preSum[right + 1] - preSum[left];
    }
}

  • 空间复杂度:定义「前缀和」数组,需要 n + 1的空间,所以空间复杂度是O(N)

  • 时间复杂度:

    • 初始化「前缀和」数组,需要把数组遍历一次,时间复杂度是 O(N)
    • 求[i,j]范围内的区间和,只用访问 preSum[j + 1]preSum[i],时间复杂度是O(1)

用途3:和为k的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        int count = 0;
        int[] array = new int[nums.length + 1];
        for (int i = 1; i <= nums.length; i++) {
            array[i] = array[i - 1] + nums[i - 1];
            if (array[i] == k) {
                count++;
            }
            // 用map记录所有前缀和,代替使用for循环判断
            if (map.containsKey(array[i] - k)) {
                count += map.get(array[i] - k);
            }
            map.put(array[i], map.getOrDefault(array[i], 0) + 1);
        }
        return count;
    }
}

拓展:二维矩阵的「前缀和」

步骤一:求 preSum

我们定义 preSum[i][j] 表示 从 [0,0] 位置到 [i,j] 位置的子矩形所有元素之和。

如果求 preSum[i][j] 的递推公式为:

nums[i + 1][j + 1] = nums[i][j + 1] + nums[i + 1][j] - nums[i][j] + matrix[i][j];

可以用下图帮助理解:
在这里插入图片描述

在这里插入图片描述

步骤二:根据 preSum 求子矩形面积

前面已经求出了数组中从 [0,0] 位置到 [i,j] 位置的 preSum

如果要求 [row1, col1][row2, col2] 的子矩形的面积的话,用 preSum 计算时对应了以下的递推公式:

nums[row2 + 1][col2 + 1] - nums[row1][col2 + 1] - nums[row2 + 1][col1] + nums[row1][col1];

同样利用一张图来说明:

在这里插入图片描述

class NumMatrix {
    int[][] nums;
    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        if(m > 0){
            int n = matrix[0].length;
            nums = new int[m + 1][n + 1];
            for(int i = 0;i < m;i++){
                for(int j = 0;j < n;j++){
                    nums[i + 1][j + 1] = nums[i][j + 1] + nums[i + 1][j] - nums[i][j] + matrix[i][j];
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return nums[row2 + 1][col2 + 1] - nums[row1][col2 + 1] - nums[row2 + 1][col1] + nums[row1][col1];
    }
}

总结

前缀和的复杂度:
1.空间复杂度是O(n)
2. 初始化的时间复杂度是 O(n)
3. 求「区间和」的时间复杂度是O(1)

参考:https://zhuanlan.zhihu.com/p/407341747

3.差分数组

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。

差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。

int[] diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
 diff[i] = nums[i] - nums[i - 1];
}
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
 res[i] = res[i - 1] + diff[i];
}

这样修改数组某个区间[i,j]时,只需要修改diff[i]diff[j+1]的值,再反推结果数组

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值