【算法】专题四.前缀和:一维二维模板,配套 LeetCode 真题,结合同余定理、哈希前缀和,搞定子数组与子矩阵区间求和问题。

二.前缀和简介:

前缀和是一种快速求解区间和的预处理算法。

核心思想:预先计算从数组起点累加到当前位置的总和存入前缀和数组;后续查询任意区间的和,只需用两个前缀和数值相减即可得到结果。

设 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.题目链接:

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


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.题目链接:

560. 和为 K 的子数组 - 力扣(LeetCode)


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.题目链接:

525. 连续数组 - 力扣(LeetCode)


3.算法思路:

稍微转化题意,就变成熟悉的前缀和题型:

  • 题目要求找出一段连续区间,区间内 0 和 1 出现次数相等。

设 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.题目链接:

1314. 矩阵区域和 - 力扣(LeetCode)


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.代码实现:

相关推荐
看浪的路人1 小时前
第1讲:2026 大模型应用开发全景——Prompt / RAG / 微调 / Agent 到底先用哪个
人工智能·算法
怕浪猫1 小时前
AI Agent 的安全怎么保障?面试最后一关的终极问题
算法·面试·github
水饺编程1 小时前
第1章,[Win32 章节]:编程环境与 MSDN
c语言·c++·windows·visual studio
晓德1 小时前
7、LLM 模型是怎么训练生成的?(4)指令微调
人工智能·学习·算法·信息安全
启观川2 小时前
数据结构与算法 -第 3 章 常用算法-回溯算法
数据结构·笔记·python·算法
零基础1232 小时前
LLM4AD:面向算法设计的大语言模型智能体框架
人工智能·算法·语言模型
草莓熊Lotso2 小时前
【C++ 基于微服务的即时通讯系统】基建三件套:gflags + gtest + spdlog 源码编译安装与实战入门
linux·网络·数据库·c++·微服务·log4j
H.莓飛2 小时前
【C++】类与对象(末):static、友元与拷贝优化
linux·开发语言·c++·visual studio
启观川10 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法