当前位置: 首页 > news >正文

前缀和--原理详解与常见变式(C/C++ 实现)

前缀和

原理详解与常见变式(C/C++ 实现)


目录

  1. 基本原理
    • 1.1 什么是前缀和
    • 1.2 一维前缀和
    • 1.3 二维前缀和
  2. 常见变式
    • 2.1 区间和查询
    • 2.2 差分数组
    • 2.3 环形前缀和
    • 2.4 字符串前缀和(哈希)
  3. 刷题技巧与模板
  4. 实战例题
  5. 总结

1. 基本原理

1.1 什么是前缀和

前缀和(Prefix Sum)是一种预处理技术,通过预先计算"累加和"来加速区间查询。

核心思想:

  • 将 O(n) 的区间求和查询优化到 O(1)
  • 以空间换时间,先遍历一次数组构造前缀和数组,之后每次查询只需一次减法
  • 广泛应用于子数组和、区间计数、矩阵求和等场景

1.2 一维前缀和

给定数组 arr,长度为 n,前缀和数组 prefix 定义为:

prefix[i] = arr[0] + arr[1] + ... + arr[i] (0 <= i < n)

约定 prefix[-1] = 0,则任意区间 [l, r] 的和可以表示为:

sum(l, r) = prefix[r] - prefix[l - 1]

对于闭区间 [l, r](l <= r),还有一种等价形式:

sum(l, r) = prefix[r + 1] - prefix[l] // 常用形式
C++ 实现(一维)
// 标准实现(推荐形式)vector<int>prefix(n+1);prefix[0]=0;for(inti=0;i<n;i++){prefix[i+1]=prefix[i]+arr[i];// prefix[i] = arr[0..i-1] 的和}// 查询区间 [l, r](闭区间,0-indexed)intsumRange(intl,intr){returnprefix[r+1]-prefix[l];}

1.3 二维前缀和

二维前缀和用于矩阵求和场景。设矩阵为 mat,行数为 m,列数为 n:

prefix[i][j] = mat[0..i-1][0..j-1] 所有元素的和

推导公式:

prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1] + mat[i-1][j-1]
C++ 实现(二维)
vector<vector<int>>prefix(m+1,vector<int>(n+1,0));for(inti=1;i<=m;i++){for(intj=1;j<=n;j++){prefix[i][j]=prefix[i-1][j]+prefix[i][j-1]-prefix[i-1][j-1]+mat[i-1][j-1];}}// 查询子矩阵 [(x1,y1), (x2,y2)](闭区间)intsumMatrix(intx1,inty1,intx2,inty2){returnprefix[x2+1][y2+1]-prefix[x1][y2+1]-prefix[x2+1][y1]+prefix[x1][y1];}

2. 常见变式

2.1 区间和查询(最基础变式)

适用场景:频繁查询固定数组的任意区间和,且数组值在查询期间不变。

关键点

  • 前缀和数组长度 = n + 1,首元素为 0(下标对齐更清晰)
  • 查询时下标 +1,避免越界判断
  • 适用于静态数组;数组频繁修改时需用树状数组或线段树

2.2 差分数组(区间更新 + 单点查询)

差分是前缀和的逆运算,用于"批量区间更新,单点查询"场景。

给定数组 arr,差分数组 diff 定义为:

diff[i] = arr[i] - arr[i-1] (i >= 1) diff[0] = arr[0]

对区间 [l, r] 执行加 val 操作,只需:

diff[l] += val; diff[r + 1] -= val; // r+1 < n 时有效,否则忽略

最后对 diff 做前缀和恢复原数组:

arr[i] = arr[i-1] + diff[i]
C++ 实现(差分)
// 构造差分数组vector<int>diff(n+1,0);diff[0]=arr[0];for(inti=1;i<n;i++){diff[i]=arr[i]-arr[i-1];}// 区间更新 [l, r] += valvoidrangeAdd(intl,intr,intval){diff[l]+=val;if(r+1<n)diff[r+1]-=val;}// 恢复原数组(做前缀和)for(inti=1;i<n;i++){diff[i]+=diff[i-1];}

2.3 环形前缀和(处理循环数组)

当问题涉及"环形结构"(如环形子数组最大和)时,将数组拼接一份到末尾:

extended = arr + arr // 长度翻倍

在 extended 上做前缀和,然后通过控制窗口大小来模拟环形。

C++ 实现(环形)
vector<int>arr2=arr;// 复制一份arr2.insert(arr2.end(),arr.begin(),arr.end());// 拼接// 在 arr2 上做前缀和vector<longlong>prefix(n*2+1,0);for(inti=0;i<n*2;i++){prefix[i+1]=prefix[i]+arr2[i];}// 长度为 k 的环形子数组和 = prefix[i+k] - prefix[i]

2.4 字符串前缀和(哈希)

将字符映射为整数,用前缀和实现 O(1) 字符串哈希,快速判断子串是否相等。

常用方法:

  • Rabin-Karp 哈希:基于进制多项式
  • 自然溢出哈希:利用 64 位无符号整数溢出
C++ 实现(字符串前缀和哈希)
constlonglongP=131;vector<longlong>prefix(n+1,0);for(inti=0;i<n;i++){prefix[i+1]=prefix[i]*P+(s[i]-'a'+1);}// 哈希值(l, r 为闭区间)longlonggetHash(intl,intr){returnprefix[r+1]-prefix[l]*powP[r-l+1];}

3. 刷题技巧与模板

3.1 何时用前缀和

场景适用数据结构复杂度
静态区间求和前缀和数组O(n) 预处理,O(1) 查询
批量区间更新 + 单点查询差分数组O(1) 更新,O(n) 还原
批量区间更新 + 区间查询树状数组 / 线段树O(log n) 更新和查询
环形数组问题拼接数组 + 前缀和O(n) 预处理,O(1) 查询

3.2 常见错误

  • 前缀和数组长度忘 +1,导致越界
  • 区间端点混淆(闭区间 vs 左闭右开)
  • 负数参与取模时未做调整
  • 二维前缀和减法忘加回去漏项
  • 环形问题窗口大小超过 n 未做限制

4. 实战例题

例题 1:和为 K 的子数组(LeetCode 560)

问题:给定整数数组 nums 和整数 k,返回和为 k 的连续子数组个数。

思路:前缀和 + 哈希表。用哈希表记录每个前缀和出现的次数,遍历时检查 (prefix - k) 是否在哈希表中。

intsubarraySum(vector<int>&nums,intk){unordered_map<int,int>cnt;cnt[0]=1;intprefix=0,ans=0;for(intnum:nums){prefix+=num;ans+=cnt[prefix-k];// 找之前出现多少次 prefix - kcnt[prefix]++;}returnans;}

例题 2:二维矩阵求和(LeetCode 304)

问题:给定二维矩阵,请求子矩阵的元素和,支持多次查询。

思路:二维前缀和预处理。

classNumMatrix{vector<vector<int>>prefix;public:NumMatrix(vector<vector<int>>&matrix){intm=matrix.size(),n=matrix[0].size();prefix.assign(m+1,vector<int>(n+1,0));for(inti=1;i<=m;i++){for(intj=1;j<=n;j++){prefix[i][j]=prefix[i-1][j]+prefix[i][j-1]-prefix[i-1][j-1]+matrix[i-1][j-1];}}}intsumRegion(intx1,inty1,intx2,inty2){returnprefix[x2+1][y2+1]-prefix[x1][y2+1]-prefix[x2+1][y1]+prefix[x1][y1];}};

例题 3:航班预订统计(LeetCode 1109)

问题:n 个航班,预订记录 bookings = [i, j, k] 表示从 i 到 j 的航班增加 k 个座位,求最终每个航班的座位数。

思路:差分数组。

vector<int>corpFlightBookings(vector<vector<int>>&bookings,intn){vector<int>diff(n+1,0);for(auto&b:bookings){inti=b[0]-1,j=b[1]-1,k=b[2];// 转为 0-indexeddiff[i]+=k;if(j+1<n)diff[j+1]-=k;}vector<int>ans(n);ans[0]=diff[0];for(inti=1;i<n;i++)ans[i]=ans[i-1]+diff[i];returnans;}

5. 总结

前缀和是一种强大的预处理技术,核心公式只有两个:

  • 一维prefix[i+1] = prefix[i] + arr[i],区间和 =prefix[r+1] - prefix[l]
  • 二维prefix[i][j] = mat[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]

其变式——差分数组、环形扩展、字符串哈希——都是这一思想的延伸。

熟练掌握前缀和,可以将许多 O(n) 甚至 O(n²) 的题目优化到 O(1) 查询,是算法竞赛和面试中的必备技能。

http://www.jsqmd.com/news/1081989/

相关文章:

  • 微电网混合控制架构的应用案例
  • 为什么92%的Eclipse老手改用IDEA后效率反降?真相藏在这43组快捷键语义差异里,立即自查!
  • Blender 3MF插件:让3D打印设计更简单的专业工具
  • LRCGET:离线音乐库批量歌词下载终极指南 [特殊字符]
  • 【网络安全】CTF竞赛介绍
  • 收藏!2026年技术小白也能看懂的大模型学习路线图,速进!
  • 勒索病毒应急响应实战:从隔离取证到系统加固的完整指南
  • 资源下载神器:5分钟搞定全网视频音频快速保存
  • 5分钟解决Windows和Office激活难题的智能方案
  • 为什么头部云厂商悄悄弃用VMware?2024Q2真实迁移案例拆解(含成本节省217万原始报表)
  • 深度解析iOS端U2-Net背景移除架构设计与性能优化
  • KMS智能激活工具:一站式解决Windows与Office激活难题
  • 10分钟学会ExifToolGUI:免费开源的图片元数据管理神器
  • VMware替代不是替换,而是重构:Gartner认证的5层迁移成熟度模型(附自评工具)
  • 文本嵌入实战:用OpenAI ada-002构建语义聚类流水线
  • NanaZip完整指南:3种方法掌握Windows平台最佳压缩工具
  • linux内核中一个特殊宏:BUILD_BUG_ON的分析
  • 从POC到采购决策:商用AI快速开发工具成本、收费模式与ROI验证全攻略
  • 高精度厨房秤整体解决方案
  • 移动端系统镜像提取革命:Payload-Dumper-Android颠覆传统工作流
  • 多账号矩阵引流实操全指南:分层布局、5 种落地玩法与风控避坑
  • 免费开源鼠标连点器:3分钟掌握自动化点击技巧
  • MusicBee网易云歌词插件终极指南:3步实现完美同步歌词体验
  • 【嵌入式评测】youyeetoo R1 V3.0(RK3588S)开发板全解析|全参数解析、部署教程与性能实测
  • HoRain云--C++ 基本语法
  • 2026年7款可视化项目管理软件对比:从团队协同到企业级交付
  • 5分钟搞定:Mac免费读写NTFS硬盘的终极解决方案
  • 从评估板到实战:深度解析多相数字降压电源设计
  • 告别网盘限速:LinkSwift 九大网盘直链下载终极指南
  • 【博通收购VMware终极指南】:免费版VCSA/ESXi能否续用?3大官方政策红线与5种替代方案速查