跳到主要内容 前缀和算法详解与经典例题解析 | 极客日志
C++ 算法
前缀和算法详解与经典例题解析 详细讲解了一维和二维前缀和算法的原理及实现,并通过六个经典力扣(LeetCode)例题展示了前缀和及其变体在区间求和、中心下标查找、乘积计算、子数组统计等场景中的应用。内容涵盖预处理技巧、边界处理、哈希表优化及负数取余修正,旨在帮助读者掌握 O(1) 查询区间和的高效解法。
虚拟内存 发布于 2026/3/25 更新于 2026/4/18 4 浏览一维前缀和
题目链接 : 牛客网
描述
给定一个数组,进行多次区间求和查询。
输入描述
第一行包含两个整数 n, q。n 表示数组大小,q 表示查询次数。
接下来一行 n 个整数表示数组元素。
接下来 q 行,每行两个整数 l, r,表示查询区间。
输出描述
输出 q 行,每行代表一次查询的结果。
示例
输入:
3 2
1 2 4
1 2
2 3
输出:
3
6
该题第一行输入 3 2 的意思是数组大小为 3,需进行 2 次查询。第二行输入的 n 个整数表示数组中的元素。后面的 2 行是计算数组中从 l 到 r 的和。
如果使用暴力解法解决这个题,时间复杂度会超时。因此需要使用前缀和算法,可以快速得出某一段区间之和的大小,时间复杂度为 O(1)。
第一步:预处理出来一个前缀和数组 dp。
dp[i] 表示 [1, i] 区间内所有元素的和。
dp[3] 表示数组第一个元素到第三个元素的和。
递推公式:dp[i] = dp[i-1] + arr[i]
疑问:为什么我们的 dp 下标从 1 开始计数?
假设我们想求出 [0, 2] 区间的大小,利用 dp 得到 dp[2] - dp[-1],这会导致越界。如果从 1 开始,则不会干扰最终结果,主要是为了处理边界情况。
#include <iostream>
#include <vector>
using namespace std;
int main () {
int n, q;
cin >> n >> q;
vector<int > arr (n + 1 ) ;
for (int i = 1 ; i <= n; i++) cin >> arr[i];
vector<long long > dp (n + 1 ) ;
for (int i = 1 ; i <= n; i++) dp[i] = dp[i - 1 ] + arr[i];
int l = 0 , r = 0 ;
while (q--) {
cin >> l >> r;
cout << dp[r] - dp[l - ] << endl;
}
;
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
1
return
0
二维前缀和 描述
给你一个 n 行 m 列的矩阵 A,下标从 1 开始。接下来有 q 次查询,每次查询输入 4 个参数 x1, y1, x2, y2。请输出以 (x1, y1) 为左上角,(x2, y2) 为右下角的子矩阵的和。
输入描述
第一行包含三个整数 n, m, q。
接下来 n 行,每行 m 个整数,代表矩阵的元素。
接下来 q 行,每行 4 个整数 x1, y1, x2, y2。
示例 1
输入:
3 4 3
1 2 3 4
3 2 1 0
1 5 7 8
1 1 2 2
1 1 3 3
1 2 3 4
预处理出来一个前缀和矩阵。
dp[i][j] 表示从 (1,1) 到位置 [i,j] 这段区间里面所有元素的和。
递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1]
使用前缀和矩阵。
子矩阵面积等于:dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]
#include <iostream>
#include <vector>
using namespace std;
int main () {
int n = 0 , m = 0 , q = 0 ;
cin >> n >> m >> q;
vector<vector<int >> arr (n + 1 , vector <int >(m + 1 ));
for (int i = 1 ; i <= n; i++) {
for (int j = 1 ; j <= m; j++) {
cin >> arr[i][j];
}
}
vector<vector<long long >> dp (n + 1 , vector <long long >(m + 1 ));
for (int i = 1 ; i <= n; i++) {
for (int j = 1 ; j <= m; j++) {
dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ] + arr[i][j] - dp[i - 1 ][j - 1 ];
}
}
int x1 = 0 , y1 = 0 , x2 = 0 , y2 = 0 ;
while (q--) {
cin >> x1 >> y1 >> x2 >> y2;
cout << dp[x2][y2] - dp[x1 - 1 ][y2] - dp[x2][y1 - 1 ] + dp[x1 - 1 ][y1 - 1 ] << endl;
}
return 0 ;
}
寻找数组的中心下标 描述
给你一个整数数组 nums,请计算数组的中心下标。中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果不存在返回 -1。
示例 1
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:左侧数之和 sum = 1 + 7 + 3 = 11,右侧数之和 sum = 5 + 6 = 11。
利用前缀和解决这个问题。
预处理:
f:前缀和数组,f[i] 表示 [0, i-1] 区间所有元素的和。
f[i] = f[i-1] + nums[i-1]
g:后缀和数组,g[i] 表示 [i+1, n-1] 区间所有元素的和。
g[i] = g[i+1] + nums[i+1]
细节问题:当我们将 f[0] 带进去的话会出现越界的情况,g[n-1] 也会出现问题。两个边界问题得提前处理。
class Solution {
public :
int pivotIndex (vector<int >& nums) {
int n = nums.size ();
vector<int > f (n) , g (n) ;
for (int i = 1 ; i < n; i++) {
f[i] = f[i - 1 ] + nums[i - 1 ];
}
for (int i = n - 2 ; i >= 0 ; i--) {
g[i] = g[i + 1 ] + nums[i + 1 ];
}
for (int i = 0 ; i < n; i++) {
if (f[i] == g[i]) return i;
}
return -1 ;
}
};
除自身以外数组的乘积 描述
给你一个整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。请不要使用除法,且在 O(n) 时间复杂度内完成此题。
示例 1
输入:nums = [1, 2, 3, 4]
输出:[24, 12, 8, 6]
预处理前缀积和后缀积。
f:表示前缀积,f[i] 表示 [0, i-1] 区间内所有元素的乘积。
f[i] = f[i-1] * nums[i-1]
g:表示后缀积,g[i] = g[i+1] * nums[i+1]
处理细节问题:f[0] 和 g[n-1] 需要设置为 1,避免越界问题。
class Solution {
public :
vector<int > productExceptSelf (vector<int >& nums) {
int n = nums.size ();
vector<int > f (n) , g (n) ;
f[0 ] = g[n - 1 ] = 1 ;
for (int i = 1 ; i < n; i++) {
f[i] = f[i - 1 ] * nums[i - 1 ];
}
for (int i = n - 2 ; i >= 0 ; i--) {
g[i] = g[i + 1 ] * nums[i + 1 ];
}
vector<int > ret (n) ;
for (int i = 0 ; i < n; i++) {
ret[i] = f[i] * g[i];
}
return ret;
}
};
和为 K 的子数组 描述
给你一个整数数组 nums 和一个整数 k,请你统计并返回该数组中和为 k 的子数组的个数。
示例 1
输入:nums = [1, 1, 1], k = 2
输出:2
这个题不能使用双指针滑动窗口做优化。只需要找到 i 位置前面,即 [0, i-1] 位置中间,有多少个前缀和等于 sum[i] - k。
利用哈希表记录前缀和出现的次数。
细节问题:前缀和加入哈希表的时机,在计算 i 位置之前,哈希表里面只保存 [0, i-1] 位置的前缀和。如果整个前缀和等于 k,我们需要将 hash[0]=1 丢到哈希表中。
class Solution {
public :
int subarraySum (vector<int >& nums, int k) {
unordered_map<int , int > hash;
hash[0 ] = 1 ;
int sum = 0 , ret = 0 ;
for (auto x : nums) {
sum += x;
if (hash.count (sum - k)) ret += hash[sum - k];
hash[sum]++;
}
return ret;
}
};
和可被 K 整除的子数组 描述
给定一个整数数组 nums 和一个整数 k,返回其中元素之和可被 k 整除的非空子数组的数目。
同余定理:a 对 p 取得余数就等于 b 对 p 取的余数,前提是 (a-b) 除以 p=k...0。
即 sum[i] % k == sum[j] % k。
由于可能是负数,需要对余数进行修正:(sum % k + k) % k。
class Solution {
public :
int subarraysDivByK (vector<int >& nums, int k) {
unordered_map<int , int > hash;
hash[0 % k] = 1 ;
int sum = 0 , ret = 0 ;
for (auto x : nums) {
sum += x;
int r = (sum % k + k) % k;
if (hash.count (r)) ret += hash[r];
hash[r]++;
}
return ret;
}
};
连续数组 描述
给定一个二进制数组 nums,找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
解法:前缀和 + 哈希表。
将 0 变为 -1,问题转化为找到和为 0 的最长子数组。
哈希表中存前缀和和下标。如果有重复的 <sum, i>,只保存前面的那一对。<sum, i>。
默认的前缀和为 0 的情况,hash[0] = -1。
长度为 i - hash[sum]。
class Solution {
public :
int findMaxLength (vector<int >& nums) {
unordered_map<int , int > hash;
hash[0 ] = -1 ;
int sum = 0 , ret = 0 ;
for (int i = 0 ; i < nums.size (); i++) {
sum += nums[i] == 0 ? -1 : 1 ;
if (hash.count (sum)) ret = max (ret, i - hash[sum]);
else hash[sum] = i;
}
return ret;
}
};
矩阵区域和 描述
给你一个 m x n 的矩阵 mat 和一个整数 k,请你返回一个矩阵 answer,其中每个 answer[i][j] 是所有满足条件 i - k <= r <= i + k, j - k <= c <= j + k 且 (r, c) 在矩阵内的元素 mat[r][c] 的和。
题目要求返回和原始矩阵相同规模的矩阵。这个题起始就是快速求出某个区域的和,就是在求二维数组前缀和的基础上进行改变。
对于右下角区域的和:dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + mat[i][j]
我们在求 ans[i][j] 的时候,需要向四周扩展 k 个单位。如果中间点的坐标是 [i][j] 的话,那么左上角的坐标就是 [i-k][j-k],右下角的坐标是 [i+k][j+k]。
如果 i-k < 0,回归到 (0,0)。如果 x2 > (m-1, n-1),定义为 (m-1, n-1)。
class Solution {
public :
vector<vector<int >> matrixBlockSum (vector<vector<int >>& mat, int k) {
int m = mat.size (), n = mat[0 ].size ();
vector<vector<int >> dp (m + 1 , vector <int >(n + 1 ));
for (int i = 1 ; i <= m; i++) {
for (int j = 1 ; j <= n; j++) {
dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ] - dp[i - 1 ][j - 1 ] + mat[i - 1 ][j - 1 ];
}
}
vector<vector<int >> ret (m, vector <int >(n));
for (int i = 0 ; i < m; i++) {
for (int j = 0 ; j < n; j++) {
int x1 = max (0 , i - k) + 1 , y1 = max (0 , j - k) + 1 ;
int x2 = min (m - 1 , i + k) + 1 , y2 = min (n - 1 , j + k) + 1 ;
ret[i][j] = dp[x2][y2] - dp[x1 - 1 ][y2] - dp[x2][y1 - 1 ] + dp[x1 - 1 ][y1 - 1 ];
}
}
return ret;
}
};