二.前缀和简介:
前缀和是一种快速求解区间和的预处理算法。
核心思想:预先计算从数组起点累加到当前位置的总和存入前缀和数组;后续查询任意区间的和,只需用两个前缀和数值相减即可得到结果。
设 prei = a1 + a2 + ... + ai 区间 l, r 的区间和公式:
cpp
pre[r] - pre[l-1]
复杂度:预处理一次为 O(n),之后每一次区间查询仅需 O(1)。 对比每次查询都遍历累加的 O(n) 做法,在多次查询场景下效率提升巨大。
前缀和分为一维前缀和、二维前缀和:
- 一维前缀和:针对普通数组,求一段连续子数组的和
- 二维前缀和:针对矩阵,求任意子矩阵的和
适用场景:数组数据不会发生修改,需要多次查询区间和。
二.算法练习:
A.【模板】一维前缀和
1.题目描述:

2.题目链接:
3.算法思路:
- a. 预处理前缀和数组 :
用 dpi 代表区间 1, i 内全部元素的和;
dpi - 1 存储区间 1, i - 1 的元素和。
递推公式:dpi = dpi - 1 + arri
- b. 利用前缀和数组快速计算区间和:
查询区间为 l, r,区间元素和公式: dpr - dpl - 1

4.代码实现:

B.【模板】二维前缀和
1.题目描述:

2.题目链接:
3.算法思路:
类比一维数组前缀和,如果预处理出从 [0,0] 到 [i,j] 这片区域所有元素的累加和,就能够在 O(1) 时间内,求出矩阵中任意子区域的元素总和。整体分为两步:
- 第一步:构建前缀和矩阵
这是一维前缀和向二维的拓展。我们在原矩阵的最上方新增一行 0、最左侧新增一列 0,可以规避大量边界判断问题。(如果不补这一行一列,单独处理边界会非常繁琐)

这样,我们填写前缀和矩阵数组的时候,下标直接从 1 开始,能⼤胆使⽤ i - 1 , j - 1 位置的值。
注意 dp 表与原数组 matrix 内的元素的映射关系:
i. 从 dp 表到 matrix 矩阵,横纵坐标减⼀;
ii. 从 matrix 矩阵到 dp 表,横纵坐标加⼀。
前缀和矩阵中 sumij 的含义,以及如何递推⼆维前缀和⽅程
- a. sumij 的含义:
sumij 表⽰,从 0, 0 位置到 i, j 位置这段区域内,所有元素的累加和。对应下图的红⾊区域:

- b. 递推⽅程:
其实这个递推⽅程⾮常像我们⼩学做过求图形⾯积的题,我们可以将 0, 0 位置到 i, j位置这段区域分解成下⾯的部分:

sumij由红、蓝、绿、黄四块区域组成,区域分析:
i. 黄色区域:对应原数组
matrix[i - 1][j - 1](注意坐标映射关系)ii. 蓝色、绿色区域单独无法用 dp 状态直接求出;
iii. 红 + 蓝 整体,等价于
sum[i - 1][j];iv. 红 + 绿 整体,等价于
sum[i][j - 1];v. 将上面三项相加:黄 + 红 + 蓝 + 红 + 绿,红色区域被重复计算一次,需要减去一次红色区域;
vi. 红色区域对应
sum[i - 1][j - 1]。
综上,二维前缀和递推公式:
cpp
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1]
- 第⼆步:使⽤前缀和矩阵
题⽬的接⼝中提供的参数是原始矩阵的下标,为了避免下标映射错误,这⾥直接先把下标映射成
dp 表⾥⾯对应的下标: row1++, col1++, row2++, col2++.
接下来分析如何使⽤这个前缀和矩阵,如下图(注意这⾥的 row 和 col 都处理过了,对应的正
是 sum 矩阵中的下标):

对于左上角 (row1, col1)、右下角 (row2, col2) 围成的红色区域,求该区域元素总和,区域分析:
i. 黄色区域:
sum[row1 - 1][col1 - 1];减 1 是为了剔除 row1 这一行、col1 这一列前面的部分。ii. 绿色区域单独不好计算,绿色 + 黄色合起来,等于
sum[row1 - 1][col2]。iii. 蓝色区域单独不好计算,蓝色 + 黄色合起来,等于
sum[row2][col1 - 1]。iv. 整块大区域总和,等于
sum[row2][col2]。v. 红色区域 = 整块大区域 − 黄色 − 绿色 − 蓝色;绿色、蓝色不便单独计算,变形为:整块 −(绿 + 黄)−(蓝 + 黄);此时黄色被多减去一次,需要再加回来。
综上,红色子矩阵区域和公式:
cpp
sum[row2][col2] - sum[row2][col1 - 1] - sum[row1 - 1][col2] + sum[row1 - 1][col1 - 1]

4.代码实现:

C.寻找数组的中心下标
1.题目描述:

2.题目链接:
3.算法思路:
从中心下标的定义可知:除中心下标元素本身外,该元素左侧所有元素的前缀和,等于该元素右侧所有元素的后缀和。
- 预处理两个数组,分别存储前缀和、后缀和。
- 通过 for 循环枚举每一个可能的中心下标,判断当前位置的前缀和与后缀和是否相等;若相等,则返回当前下标。

4.代码实现:

D.和为 k 的子数组
1.题目描述:

2.题目链接:
238. 除了自身以外数组的乘积 - 力扣(LeetCode)
3.算法思路:
题目限制:不能使用除法,要求时间复杂度 O(N)。因此暴力枚举、先求总乘积再除以当前元素的思路都不可行。
分析题意:
结果数组中 ret[i] 的值由两部分相乘得到:
i.
nums[0] * nums[1] * nums[2] * ... * nums[i - 1]ii.
nums[i + 1] * nums[i + 2] * ... * nums[n - 1]
借鉴前缀和思想,预处理两个数组 pre 和 suf:
i.
pre[i]:i位置前面区间[0, i - 1]所有元素的前缀乘积ii.
suf[i]:i位置后面区间[i + 1, n - 1]所有元素的后缀乘积
最后合并计算得到最终结果

4.代码实现:

E.和为 k 的子数组
1.题目描述:

2.题目链接:
3.算法思路:
设 i 为数组任意下标,sum[i] 代表区间 [0, i] 内所有元素的和。 我们要统计以 i 为结尾,区间和等于 k 的子数组个数 ,等价于寻找若干起始位置x,满足区间 [x, i] 的和为 k。
由区间和公式推导可得:[0, x-1] 的前缀和等于 sum[i] - k
问题转化为: 在区间 [0, i - 1] 中,统计前缀和等于 sum[i] - k 的出现次数。
不需要单独开辟前缀和数组;我们只需要记录前面每种前缀和一共出现多少次。 因此使用哈希表,一边遍历计算当前前缀和,一边保存历史前缀和对应的出现次数。
核心公式:sumi - sumx-1 = k --> sumx-1 = sumi - k
哈希表:key = 前缀和的值,value = 该前缀和出现次数

4.代码实现:

F.和可被 k 整除的子数组
1.题目描述:
(本题是某⼀年的蓝桥杯竞赛原题哈)

2.题目链接:
974. 和可被 K 整除的子数组 - 力扣(LeetCode)
3.算法思路:
思路与560. 和为 K 的子数组 - 力扣(LeetCode)这道题的思路相似。
设 i 为数组中的任意位置,sum[i] 表示 [0, i] 区间内所有元素的和。
- 想知道有多少个以 i 为结尾、区间和可以被 k 整除的子数组,就要找到若干起始位置
x,使得[x, i]的区间和能够被 k 整除。- 设
[0, x - 1]的前缀和为a,[0, i]的前缀和为b,可得关系式:(b - a) % k == 0。- 根据同余定理:
b % k == a % k,也就是[0, x-1]和[0, i]的前缀和对 k 取模余数相等。问题转化为: 在区间[0, i - 1]内,统计前缀和取模后等于sum[i] % k的数量。
不需要单独开辟前缀和数组。我们只关心 i 之前,每种前缀和余数出现多少次。因此使用哈希表,一边遍历计算当前前缀和,一边记录每种余数的出现次数。

- 同余定理
如果 (a - b) % n == 0 ,那么我们可以得到⼀个结论: a % n == b % n 。⽤⽂字叙述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。
例如: (26 - 2) % 12 == 0 ,那么 26 % 12 == 2 % 12 == 2 。
- c++ 中负数取模的结果,以及如何修正「负数取模」的结果
a. c++ 中关于负数的取模运算,结果是把负数当成正数,取模之后的结果加上⼀个负号 。
例如: -1 % 3 = -(1 % 3) = -1
b. 因为有负数,为了防⽌发⽣出现负数 的结果,以 (a % n + n) % n 的形式输出保证为正。
例如: -1 % 3 = (-1 % 3 + 3) % 3 = 2
4.代码实现:

G.连续数组
1.题目描述:

2.题目链接:
3.算法思路:
稍微转化题意,就变成熟悉的前缀和题型:
- 题目要求找出一段连续区间,区间内 0 和 1 出现次数相等。
- 把数组中的 0 替换成 - 1,1 保持为 1,问题就转化为:寻找一段区间,区间元素和等于 0。
- 思路和 560. 和为 K 的子数组 - 力扣(LeetCode)一致。
设
i为数组中的任意位置,sum[i]表示[0, i]区间内所有元素的和。 想要找到以 i 为结尾,和为 0 的最长子数组 ,需要找到最左侧位置x1,使得区间[x1, i]的和等于 0。 根据区间和公式推导可得:[0, x1 - 1]的前缀和等于sum[i]。问题转化为:
- 在区间
[0, i - 1]内,找到第一次出现sum[i]的下标位置。
不需要单独开辟前缀和数组。我们只关心 i 之前,每种前缀和第一次出现的索引。因此使用哈希表,一边遍历计算当前前缀和,一边记录每个前缀和首次出现的位置。
核心:哈希表存前缀和值 --\> 首次出现下标,相同前缀和只存最早位置,保证子数组长度最大。

4.代码实现:

H.矩阵区域和
1.题目描述:

2.题目链接:
3.算法思路:
⼆维前缀和的简单应⽤题,关键就是我们在填写结果矩阵的时候,要找到原矩阵对应区域的左上
⻆以及右下⻆的坐标(推荐⼤家画图)
左上⻆坐标: x1 = i - k,y1 = j - k ,但是由于会超过矩阵的范围,因此需要对 0取⼀个 max 。因此修正后的坐标为: x1 = max(0, i - k), y1 = max(0, j - k) ;
右下⻆坐标: x1 = i + k,y1 = j + k ,但是由于会超过矩阵的范围,因此需要对 m- 1 ,以及 n - 1 取⼀个 min 。因此修正后的坐标为: x2 = min(m - 1, i + k),y2 = min(n - 1, j + k) 。
然后将求出来的坐标代⼊到⼆维前缀和矩阵的计算公式上即可(但是要注意下标的映射关
系)

4.代码实现:
