前缀和算法

目录

1.一维前缀和

1.1.什么是一维前缀和

1.2.预处理获得一维前缀和数组

1.3.使用一维前缀和数组

 2.二维前缀和 

2.1.什么是二维前缀和

2.2.预处理一个前缀和矩阵

2.3.怎么计算两点之间的二维前缀和

题目一( 一维前缀和模板)——【模板】前缀和_牛客题霸_牛客网

1.1.暴力解法

1.2.一维前缀和解法 

题目二(二维前缀和模板)—— 【模板】二维前缀和_牛客题霸_牛客网

2.1.使用二维前缀和

  题目三——724. 寻找数组的中心下标 - 力扣(LeetCode)

3.1.暴力解法

3.2.一维前缀和

3.3.前缀和与后缀和解法

题目四——238. 除自身以外数组的乘积 - 力扣(LeetCode)

4.1.暴力解法

4.2.前缀积与后缀积解法

题目五——560. 和为 K 的子数组 - 力扣(LeetCode)

5.1.暴力解法

5.2.前缀和+哈希表

题目六——974. 和可被 K 整除的子数组 - 力扣(LeetCode)

6.1.补充知识

6.2.暴力解法

6.3.前缀和+哈希表(unordered_map模拟)

题目七——525. 连续数组 - 力扣(LeetCode)

7.1.暴力解法

7.2.前缀和+哈希表(unordered_map模拟) 

题目八——1314. 矩阵区域和 - 力扣(LeetCode)

8.1.二维前缀和解法


 

1.一维前缀和

1.1.什么是一维前缀和

前缀和其实我们很早之前就了解过的,我们求数列的和时,Sn = a1+a2+a3+...an; 此时Sn就是数列的前 n 项和。例 S5 = a1 + a2 + a3 + a4 + a5; S2 = a1 + a2。所以我们完全可以通过 S5-S2 得到 a3+a4+a5 的值,这个过程就和我们做题用到的前缀和思想类似。

首先,先记忆下面这两点

  • 前缀和是需要借助一个数组的。所以我们需要创建一个数组presum.
  • 我们的前缀和数组presum[n]里保存的就是原数组nums前 n 项的和。

见下图

我们通过前缀和数组保存前 n 位的和,

  • presum[1]保存的就是 nums 数组中前 1 位的和,也就是 presum[1] = nums[0];
  • presum[2]保存的就是 nums 数组中前 2 位的和,也就是 presum[2] = nums[0] + nums[1] = presum[1] + nums[1].
  • .......
  • 依次类推,所以我们通过前缀和数组可以轻松得到前缀和数组presum[n]里保存的就是原数组nums前 n 项的和。

1.2.预处理获得一维前缀和数组

我们说:前缀和数组presum[n]里保存的就是原数组nums前 n 项的和。

  • presum[1] = nums[0];
  • presum[2] = nums[0]+nums[1]=presum[1]+nums[1]
  • presum[3] = nums[0]+nums[1]+nums[2]=presum[2]+nums[2]
  • presum[4] = nums[0]+nums[1]+nums[2]+nums[3]=presum[3]+nums[3]
  • presum[5] = nums[0]+nums[1]+nums[2]+nums[3]+nums[4]=presum[4]+nums[4]
  • ..........

我们很快就能得出求和数组的递推公式:

presum[i]=presum[i-1]+nums[i-1];

但是我们仔细看,i=1的时候,怎么有一点不一样?

按道理来说presum[1]=presum[0]+nums[0];的,但是它却是presum[1] = nums[0];

这么来说,presum[0]就应该等于0。

为什么前缀和数组presum比原数组nums多一个元素啊?

  • 前缀和数组presum比原数组nums多一个元素的意义就可以体现出来了,当我们求第一个元素数组的时候需要加上前一个presum[0] 。
  • 但是如果第一个元素前一个位置没有东西就会发生越界访问,因此我们要给他提前准备一个内存并且默认为0。
  • 总的来说,就是为了处理第一个元素越界的问题。

好啦,我们已经了解了前缀和的解题思想了,我们可以通过下面这段代码得到我们的前缀和数组,非常简单

 for (int i = 0; i < nums.length(); i++) {
      presum[i+1] = nums[i] + presum[i];
 }

1.3.使用一维前缀和数组

  • 我们需要获取 nums[x1] 到 nums[x2] 这个区间的和,那么我们怎么根据 presum 数组获取 nums[x1] 到 nums[x2] 区间的和呢?

我们知道

nums[x1]是nums的第  x1+1  个数, nums[x2]是nums数组的第 x2+1 个数。

题目叫我们求 nums[x1] 到 nums[x2] 这个区间的和,也就是nums数组第  x1+1  个数到第 x2+1 个数的和。

那么就使用presum[x2+1]-presum[x1](这个意思就是原数组前x2项的和-原数组前x1项的和,剩下的就是我们要求的

例如下面的求nums[2]到nums[4]区间的元素之和,就用presum[5]-presum[2]

 2.二维前缀和 

2.1.什么是二维前缀和

在二维前缀和数组中,每个元素prefixSum[i][j]存储的是原矩阵中从(0, 0)(i-1, j-1)的矩形区域内所有元素的和(注意,这里的索引通常是从1开始的,但也可以从0开始,只需相应地调整计算方式)。

 我们看下图,(x1,y1)的二维前缀和就是图中的蓝色部分的所有元素(包括(x1,y1)的和)

2.2.预处理一个前缀和矩阵

  • 这个时候我们应该创建一个新的矩阵,假设原有数组为a[n][n]大,那这个二维前缀和矩阵的大小就应该是presum[n+1][n+1]

这⾥就要⽤到⼀维数组⾥⾯的拓展知识,我们要在矩阵的最上⾯和最左边添加上⼀⾏和⼀列 0,这样我们就可以省去⾮常多的边界条件的处理(同学们可以⾃⾏尝试直接搞出来前缀和矩 阵,边界条件的处理会让你崩溃的)。

处理后的矩阵就像这样:

这样,我们填写前缀和矩阵数组的时候,下标直接从 1 开始,能⼤胆使⽤ i - 1 , j - 1 位 置的值。


注意:本文没有特别说明的情况下,都是假设原二维数组的下标是从0开始的 

  • presum[i][j]的含义:表示原数组里面从(0,0)这个位置到(i-1,j-1)所有元素的和

怎么求presum[i][j]?

首先我们得知道presum[i][j]的含义就是表示原数组里面从(0,0)这个位置到(i-1,j-1)所有元素的和

我们仔细看下面这个图(这个图是原数组的),假设A点arr[i-1,j-1],B点arr[i,j-1],C点arr[i-1,j],D点就是arr[i,j],那么它的二维前缀和就是区域1+区域2+区域3+区域4内的所有元素的和

注意D点的二维前缀和是presum[i+1][j+1]。

注意 presum 表与原数组 arr 内的元素的映射关系:

  • 从 presum 表到 arr 矩阵,横纵坐标减⼀;
  • 从 arr 矩阵到 presum 表,横纵坐标加⼀(我们下面用这个)。
  • 区域1最好算,就是presum[i][j]
  • 单独的区域2或者单独的区域3不好算
  • 如果将区域2和区域1合在一起算就好算了,就是B(i,j-1)点的二维前缀和,就是presum[i+1][j]
  • 如果将区域3和区域1合在一起算就好算了,就是C(i-1,j)点的二维前缀和,就是presum[i][j+1]
  • 这个时候presum[i][j]=区域2和区域1+区域3和区域1+区域4-区域1
  • 有人想说区域4怎么算?我只想说,区域4不就只有一个元素吗?就是原数组的nums[i][j]

即presum[i+1][j+1]=presum[i+1][j]+presum[i][j+1]-presum[i][i]+arr[i][j]

        但是因为我们的二维前缀和数组的最上⾯和最左边都是⼀⾏和⼀列 0,这意味着x=0和y=0的时候,该点的数据都是没意义的。我们都是从下标为1的元素开始使用的,保证了i-1和j-1一定不会越界。

所以我们可以把公式改成下面这样子

  • presum[i][j]=presum[i][j-1]+presum[i-1][j]-presum[i-1][i-1]+arr[i-1][j-1]

注意这个是在原数组是从下标0开始使用的情况下的公式

2.3.怎么计算两点之间的二维前缀和

注意 presum 表与原数组 arr 内的元素的映射关系:

  • 从 presum 表到 arr 矩阵,横纵坐标减⼀;
  • 从 arr 矩阵到 presum 表,横纵坐标加⼀(我们下面用这个)。

在二维空间中,计算两点之间的前缀和通常意味着我们需要计算一个由这两个点定义的矩形区域内所有元素的总和。这个矩形区域以给定的两点为对角线的两个端点。

注意:下面的图都是在原数组arr上进行划分的

我们看下图,(x1,y1)的二维前缀和就是图中的蓝色部分的所有元素的和,注意arr[x1,y1]在二维前缀和数组presum里面可是presum[x1+1,y1+1]

我们再搞一个(x2,y2),这个点的二维前缀和就是图中的红色部分的所有元素的和,注意arr[x2,y2]在二维前缀和数组presum里面可是presum[x2+1,y2+1]

 如果说要求(x1,y1)和(x2,y2)之间的二维前缀和,也就是下面的黑色部分

这个时候我们标出4个点的位置

 

  • D点(arr[x2,y2])的表示的二维前缀和值(presum[x2+1,y2+1])是蓝色部分+两个红色部分+黑色部分
  • A点(arr[x1,y1])表示的二维前缀和值(presum[x1+1,y1+1])是蓝色部分
  • B点(arr[x2,y1])表示的二维前缀和值(presum[x2+1,y1+1])是上面的红色部分+蓝色部分
  • C点(arr[x1,y2])表示的二维前缀和值(presum[x1+1,y2+1])是下面的红色部分+蓝色部分

这样是不是发现有什么神奇的东西快要出现了

  • 这里面只有D的前缀和里面包括黑色部分

只要减去D里面的哪两个黄色部分和红色部分是不是就剩下了我们要求的黑色部分了?
那怎么减去呢?
可以这样:

  • D - B - C + A

也就是presum[x2+1,y2+1] - presum[x2+1,y1+1] - presum[x1+1,y2+1] + presum[x1+1,y1+1]

但是对不对呢?

答案是不对的!!!为什么呢?

首先presum[x2+1,y2+1] - presum[x2+1,y1+1] - presum[x1+1,y2+1] + presum[x1+1,y1+1]算出来的那个范围是不包括(x1,y1)这点以及下面圈起来的那些点的

但是我们要求的可是要包括这些元素的,那咋办?

那就只能将将范围移动一下,让x1变成x1-1,y1变成y1-1,这样子就能保证上面说的那些点在结果里面啦!

这个思想和一维前缀和数组是一样的。

所以求arr[x2,y2]到arr[x1,y1]之间的二维前缀和(包括arr[x1,y1]的)的最终的公式就是

  • presum[x2+1,y2+1] - presum[x2+1,y1] - presum[x1,y2+1] + presum[x1,y1]

注意这个是在原数组是从下标0开始使用的情况下的公式

题目一( 一维前缀和模板)——【模板】前缀和_牛客题霸_牛客网


我知道大家有一点懵,我给大家重新解读一下 

 题目描述

  • 你有一个数组 a,这个数组有 n 个元素,记作 a[1], a[2], ..., a[n]特别注意这里是从1开始的,这意味着我们要开创n+1大小的数组
  • 接下来,你将会收到 q 次查询。每次查询都会给你两个数字 l 和 r
  • 对于每次查询,你需要计算从 a[l] 到 a[r](包括 a[l] 和 a[r])之间所有元素的和,并输出这个和。

输入描述

  1. 第一行包含两个整数 n 和 q
    • n 表示数组 a 的长度。
    • q 表示查询的次数。
  2. 第二行包含 n 个整数,这些整数是数组 a 的元素:
    • 第一个数是 a[1],第二个数是 a[2],依此类推,直到 a[n]
  3. 接下来的 q 行,每行都包含两个整数 l 和 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值