目录
一、算法定义
前缀和(Prefix Sum)是一种预处理技术,通过构建一个辅助数组存储原数组前n项的和,将区间和的查询时间复杂度从O(n)优化到O(1)。其核心思想是空间换时间,适用于静态数组的频繁区间求和场景。
二、算法原理
-
preSum数组构建:
-
preSum[0] = 0(边界条件)
-
preSum[i] = Σarr[0..i-1](i≥1)
-
数学公式:preSum[i] = preSum[i-1] + arr[i-1]
-
-
区间和计算:
-
区间[i,j]的和 = preSum[j+1] - preSum[i]
-
索引对齐原理:preSum数组比原数组长度+1
-
三、C++实现代码
#include <vector>
using namespace std;
class PrefixSum {
private:
vector<int> preSum;
public:
// 预处理构造函数 O(n)
PrefixSum(const vector<int>& nums) {
int n = nums.size();
preSum.resize(n + 1, 0);
for (int i = 1; i <= n; ++i) {
preSum[i] = preSum[i-1] + nums[i-1];
}
}
// 查询区间和 O(1)
int query(int left, int right) {
return preSum[right + 1] - preSum[left];
}
// 获取前缀和数组(调试用)
vector<int> getPrefixArray() {
return preSum;
}
};
四、关键实现细节
-
索引偏移处理:
-
原数组索引i对应preSum[i+1]
-
示例:nums[0] → preSum[1]
-
-
防止溢出:
-
对于大数场景应使用long类型:
vector<long> preSum(n+1, 0L);
-
-
空数组处理:
-
构造函数需检查nums是否为空:
if (nums.empty()) throw invalid_argument("Empty input");
-
-
区间合法性校验:
if (left < 0 || right >= preSum.size()-1 || left > right) throw out_of_range("Invalid range");
五、复杂度分析
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
构造函数 | O(n) | O(n) |
query() | O(1) | O(1) |
六、典型应用场景
-
高频区间求和:
// 频繁调用示例 vector<int> nums {1,3,5,7,9}; PrefixSum ps(nums); cout << ps.query(1,3); // 3+5+7=15
-
二维前缀和扩展:
// 矩阵版本预处理 preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + matrix[i-1][j-1];
-
哈希表结合应用:
// 求子数组和为k的个数 unordered_map<int, int> countMap; countMap[0] = 1; for (int sum : preSum) { if (countMap.count(sum - k)) res += countMap[sum - k]; countMap[sum]++; }
七、算法特性对比
方法 | 预处理时间 | 查询时间 | 适用场景 |
---|---|---|---|
暴力法 | O(1) | O(n) | 低频查询 |
前缀和 | O(n) | O(1) | 高频区间查询 |
线段树 | O(n) | O(logn) | 动态数据+区间操作 |
八、注意事项
-
数据修改限制:
-
原数组必须为静态数据(构建后不可修改)
-
动态修改需使用树状数组等结构
-
-
内存优化:
-
原地计算法(节省空间):
for (int i = 1; i < nums.size(); ++i) nums[i] += nums[i-1];
-
-
浮点数精度:
-
对于浮点型数据需注意累积误差
-
九、测试用例
void test() {
vector<int> testCase = {2, -1, 3, 5};
PrefixSum ps(testCase);
assert(ps.query(0, 0) == 2);
assert(ps.query(1, 3) == (-1)+3+5);
assert(ps.query(0, 3) == 2+(-1)+3+5);
}
十、算法扩展
-
差分数组:
-
前缀和的逆运算
-
适用于区间更新操作
-
-
环形数组处理:
// 处理环形数组区间和 if (left <= right) return preSum[right+1] - preSum[left]; else return preSum.back() - (preSum[left] - preSum[right+1]);
-
权重前缀和:
// 带权重的前缀和 preSum[i] = preSum[i-1] + nums[i-1] * weight[i-1];