flowchart TD
A["原数组 arr"] --> B["初始化 pre[0] = 0"]
B --> C["遍历 i from 0 to n-1"]
C --> D["pre[i+1] = pre[i] + arr[i]"]
D --> E{是否遍历完?}
E -->|否| C
E -->|是| F["前缀和数组构建完成"]
style A fill:#ffcccc
style F fill:#ccffcc
funcfindSubarraysWithSum(arr []int, target int) [][]int { ps := NewPrefixSum(arr) result := [][]int{} // 使用哈希表记录前缀和出现的索引 prefixMap := make(map[int][]int) prefixMap[0] = []int{-1} // 前缀和为0出现在索引-1(虚拟位置) for i := 0; i < len(arr); i++ { sum := ps.Sum(i) // arr[0...i] 的和 // 如果 sum - target 存在,说明存在子数组和为 target if indices, ok := prefixMap[sum-target]; ok { for _, start := range indices { result = append(result, []int{start + 1, i}) } } // 记录当前前缀和 prefixMap[sum] = append(prefixMap[sum], i) } return result }
3. 二维前缀和
扩展到二维矩阵的区间求和。
4. 其他应用
统计问题:统计满足条件的区间数量
滑动窗口:结合滑动窗口解决相关问题
动态规划:作为动态规划的优化技巧
复杂度分析
空间复杂度: O(n)
预处理时间复杂度: O(n)
查询时间复杂度: O(1)
前缀和 vs 暴力查询
graph TB
A["查询区间和"] --> B["暴力方法"]
A --> C["前缀和方法"]
B --> B1["每次查询 O(n)"]
B --> B2["m次查询 O(mn)"]
C --> C1["预处理 O(n)"]
C --> C2["每次查询 O(1)"]
C --> C3["m次查询 O(n + m)"]
style B fill:#ffcccc
style C fill:#ccffcc
// PrefixProduct 前缀积数组 type PrefixProduct struct { pre []int }
funcNewPrefixProduct(arr []int) *PrefixProduct { n := len(arr) pre := make([]int, n+1) pre[0] = 1 for i := 0; i < n; i++ { pre[i+1] = pre[i] * arr[i] } return &PrefixProduct{pre: pre} }
func(pp *PrefixProduct) Query(l, r int) int { if l < 0 || r >= len(pp.pre)-1 || l > r { return1 } // 注意:如果数组中有0,需要特殊处理 return pp.pre[r+1] / pp.pre[l] }
// PrefixMax 前缀最大值数组 type PrefixMax struct { pre []int }
funcNewPrefixMax(arr []int) *PrefixMax { n := len(arr) pre := make([]int, n+1) pre[0] = math.MinInt for i := 0; i < n; i++ { pre[i+1] = max(pre[i], arr[i]) } return &PrefixMax{pre: pre} }
func(pm *PrefixMax) MaxTo(index int) int { if index < 0 || index >= len(pm.pre)-1 { return math.MinInt } return pm.pre[index+1] }
funcmax(a, b int)int { if a > b { return a } return b }
二维前缀和
二维前缀和用于快速计算矩阵中任意矩形区域的和。
概念
graph TB
A["二维矩阵 matrix[m][n]"] --> B["二维前缀和 pre[m+1][n+1]"]
B --> C["矩形 [x1,y1] 到 [x2,y2] 的和 = pre[x2+1][y2+1] - pre[x1][y2+1] - pre[x2+1][y1] + pre[x1][y1]"]
style A fill:#e1f5ff
style B fill:#e8f5e9
style C fill:#fff3e0
构建过程
flowchart TD
A["二维矩阵"] --> B["初始化 pre[0][j] = 0, pre[i][0] = 0"]
B --> C["遍历 i from 1 to m"]
C --> D["遍历 j from 1 to n"]
D --> E["pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + matrix[i-1][j-1]"]
E --> F{是否遍历完?}
F -->|否| D
F -->|是| G["二维前缀和构建完成"]
style A fill:#ffcccc
style G fill:#ccffcc
// DifferenceArray 差分数组 type DifferenceArray struct { diff []int }
// NewDifferenceArray 从原数组创建差分数组 funcNewDifferenceArray(arr []int) *DifferenceArray { n := len(arr) diff := make([]int, n) diff[0] = arr[0] for i := 1; i < n; i++ { diff[i] = arr[i] - arr[i-1] } return &DifferenceArray{diff: diff} }
// Update 对区间 [l, r] 的所有元素加 val func(da *DifferenceArray) Update(l, r, val int) { if l < 0 || r >= len(da.diff) || l > r { return } da.diff[l] += val if r+1 < len(da.diff) { da.diff[r+1] -= val } }
// GetArray 通过前缀和恢复原数组 func(da *DifferenceArray) GetArray() []int { n := len(da.diff) arr := make([]int, n) arr[0] = da.diff[0] for i := 1; i < n; i++ { arr[i] = arr[i-1] + da.diff[i] } return arr }
graph TB
A["区间更新操作"] --> B["暴力方法"]
A --> C["差分数组"]
B --> B1["每次更新 O(n)"]
B --> B2["k次更新 O(kn)"]
C --> C1["每次更新 O(1)"]
C --> C2["k次更新 O(k)"]
C --> C3["最后恢复 O(n)"]
C --> C4["总复杂度 O(k + n)"]
style B fill:#ffcccc
style C fill:#ccffcc
应用场景
1. 区间加法
对数组的多个区间进行加法操作,最后查询结果。
问题示例:给定一个数组,进行 k 次操作,每次对区间 [l, r] 的所有元素加 val,最后输出数组。
graph TB
A["区间查询问题"] --> B["前缀和"]
A --> C["线段树"]
B --> B1["只支持区间和查询"]
B --> B2["不支持修改"]
B --> B3["实现简单"]
B --> B4["查询 O(1)"]
C --> C1["支持多种区间操作"]
C --> C2["支持单点/区间修改"]
C --> C3["实现复杂"]
C --> C4["查询 O(log n)"]
style B fill:#ccffcc
style C fill:#ccccff
特性
前缀和
线段树
查询类型
区间和
区间和、最大值、最小值等
修改操作
不支持
支持单点/区间修改
查询复杂度
O(1)
O(log n)
空间复杂度
O(n)
O(n)
实现难度
简单
复杂
前缀和 vs 树状数组
特性
前缀和
树状数组
查询类型
区间和
区间和
修改操作
不支持
支持单点修改
查询复杂度
O(1)
O(log n)
空间复杂度
O(n)
O(n)
实现难度
简单
中等
最佳实践
1. 边界处理
1 2 3 4 5 6 7
// 好的做法:在 Query 方法中检查边界 func(ps *PrefixSum) Query(l, r int) int { if l < 0 || r >= len(ps.pre)-1 || l > r { return0// 或返回错误 } return ps.pre[r+1] - ps.pre[l] }
2. 索引理解
graph TB
A["原数组索引"] --> B["前缀和数组索引"]
A --> A1["arr[0], arr[1], ..., arr[n-1]"]
B --> B1["pre[0] = 0 pre[1] = arr[0] pre[2] = arr[0]+arr[1] ... pre[n] = arr[0]+...+arr[n-1]"]
C["区间查询"] --> D["arr[l...r] 的和"]
D --> E["pre[r+1] - pre[l]"]
style A fill:#ffcccc
style B fill:#ccffcc
style E fill:#ccffcc
关键点:
pre[i] 表示 arr[0...i-1] 的和
区间 [l, r] 的和 = pre[r+1] - pre[l]
从 0 到 i 的和 = pre[i+1]
3. 负数处理
如果数组中有负数,前缀和可能为负,需要注意:
1 2 3 4 5
// 处理负数的情况 func(ps *PrefixSum) Query(l, r int) int { // 正常处理,负数不影响计算 return ps.pre[r+1] - ps.pre[l] }
4. 溢出处理
对于大数,注意整数溢出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 使用 int64 避免溢出 type PrefixSum struct { pre []int64 }
funcNewPrefixSum(arr []int) *PrefixSum { n := len(arr) pre := make([]int64, n+1) for i := 0; i < n; i++ { pre[i+1] = pre[i] + int64(arr[i]) } return &PrefixSum{pre: pre} }