在C++算法与数据结构领域,前缀和是一种时间复杂度优化利器,尤其适用于频繁查询数组区间和的场景。它通过预先计算“前缀累积和”,将原本O(n)时间的区间和查询压缩至O(1),是面试、竞赛及工程开发中高频使用的基础技巧。

一、前缀和的核心原理

1.1 定义:什么是前缀和?

前缀和(Prefix Sum)本质是一个辅助数组,其中每个元素的值等于原数组从“起始位置”到“当前位置”的所有元素之和。对于一维数组,我们通常将前缀和数组定义为pre,其数学表达式如下:

设原数组为a[1...n](注:实际开发中常将数组下标从1开始,避免处理边界0时的逻辑判断),前缀和数组pre[0...n]的定义为:

  • pre[0] = 0(哨兵位,用于简化计算)
  • pre[i] = a[1] + a[2] + ... + a[i](即前i个元素的累积和)

例如,原数组a = [1, 2, 3, 4, 5],其前缀和数组pre计算过程如下:

  • pre[0] = 0(哨兵)
  • pre[1] = a[1] = 1
  • pre[2] = a[1] + a[2] = 1 + 2 = 3
  • pre[3] = a[1] + a[2] + a[3] = 1 + 2 + 3 = 6
  • pre[4] = 1 + 2 + 3 + 4 = 10
  • pre[5] = 1 + 2 + 3 + 4 + 5 = 15

最终pre = [0, 1, 3, 6, 10, 15]

1.2 核心价值:区间和的O(1)查询

前缀和的核心作用是快速计算原数组任意区间[l, r]的和。根据前缀和的定义,区间和sum(l, r)(即a[l] + a[l+1] + ... + a[r])可通过前缀和数组推导得出:

sum(l, r) = pre[r] - pre[l-1]
推导过程:
  • pre[r] = a[1] + a[2] + ... + a[l-1] + a[l] + ... + a[r]
  • pre[l-1] = a[1] + a[2] + ... + a[l-1]
  • 两者相减后,前l-1个元素的和被抵消,剩余部分恰好是a[l]a[r]的和。

以上述a = [1,2,3,4,5]为例,若查询区间[2,4](即2+3+4=9):

  • pre[4] = 10pre[1] = 1
  • sum(2,4) = pre[4] - pre[1] = 10 - 1 = 9,结果完全正确。

1.3 时间与空间复杂度分析

前缀和的优势体现在“预处理+多次查询”的场景中,其复杂度如下:

  • 预处理阶段:遍历原数组一次,计算前缀和数组,时间复杂度为O(n)(n为原数组长度);
  • 查询阶段:每次查询仅需一次减法运算,时间复杂度为O(1),无论查询多少次,总查询时间仅为O(q)(q为查询次数);
  • 空间复杂度:需额外存储长度为n+1的前缀和数组,空间复杂度为O(n)(可优化为原地存储,见下文“优化技巧”)。

对比“暴力查询”(每次查询遍历区间[l, r],时间复杂度O(n*q)),当查询次数q较大时(如q>1000),前缀和的效率优势会呈指数级放大。

二、一维前缀和的C++实现

一维前缀和是基础,其实现流程分为“预处理前缀和数组”和“处理区间查询”两步,以下通过完整代码示例讲解。

2.1 基础实现(下标从1开始)

下标从1开始是最常用的方式,通过pre[0] = 0的哨兵位,避免处理l=1l-1=0的边界判断错误。

#include <iostream>
#include <vector>
using namespace std;int main() {// 1. 输入原数组(长度n)int n, q; // n:原数组长度,q:查询次数cin >> n >> q;vector<int> a(n + 1); // 原数组:a[1]~a[n]for (int i = 1; i <= n; ++i) {cin >> a[i];}// 2. 预处理前缀和数组prevector<int> pre(n + 1, 0); // pre[0] = 0,pre[1]~pre[n]为前缀和for (int i = 1; i <= n; ++i) {pre[i] = pre[i - 1] + a[i]; // 递推公式:当前前缀和 = 前一个前缀和 + 原数组当前元素}// 3. 处理q次区间查询while (q--) {int l, r; // 查询区间[l, r]cin >> l >> r;// 计算区间和并输出int sum = pre[r] - pre[l - 1];cout << "区间[" << l << "," << r << "]的和为:" << sum << endl;}return 0;
}
输入输出示例:
输入:
5 3  // 原数组长度5,查询3次
1 2 3 4 5  // 原数组a[1]~a[5]
1 3  // 查询[1,3]
2 4  // 查询[2,4]
3 5  // 查询[3,5]输出:
区间[1,3]的和为:6
区间[2,4]的和为:9
区间[3,5]的和为:12

2.2 优化技巧:原地存储前缀和

若原数组后续无需使用,可直接在原数组上存储前缀和,省去额外的pre数组,将空间复杂度从O(n)优化为O(1)(不考虑输入数组本身的空间)。

实现代码如下:

#include <iostream>
#include <vector>
using namespace std;int main() {int n, q;cin >> n >> q;vector<int> a(n + 1); // 原数组与前缀和数组共用for (int i = 1; i <= n; ++i) {cin >> a[i];a[i] += a[i - 1]; // 原地更新:a[i]变为前i个元素的前缀和}while (q--) {int l, r;cin >> l >> r;cout << a[r] - a[l - 1] << endl;}return 0;
}
注意事项:
  • 此优化仅适用于“原数组无需保留”的场景(如仅需查询区间和,后续不操作原数组);
  • 若原数组需后续使用(如修改元素后重新计算前缀和),则不可使用原地存储。

2.3 边界问题处理

前缀和的边界错误是新手常见问题,需重点关注以下两点:

  1. 下标从0开始的情况:若原数组下标从0开始(如a[0]~a[n-1]),前缀和数组pre[0] = 0pre[i] = a[0] + ... + a[i-1],此时区间[l, r](0-based)的和为pre[r+1] - pre[l]。示例代码如下:
    vector<int> a = {1,2,3,4,5};
    int n = a.size();
    vector<int> pre(n + 1, 0);
    for (int i = 1; i <= n; ++i) {pre[i] = pre[i-1] + a[i-1]; // a[i-1]是原数组第i个元素
    }
    // 查询区间[1,3](0-based,即2+3+4)
    int l = 1, r = 3;
    int sum = pre[r+1] - pre[l]; // pre[4]-pre[1] = 10-1=9
    
  2. 数据溢出问题:若原数组元素为int类型且数值较大(如a[i]为1e9,n为1e5),前缀和可能超过int的最大值(2^31-1 ≈ 2e9),此时需将前缀和数组类型改为long long。示例:
    vector<long long> pre(n + 1, 0); // 用long long避免溢出
    for (int i = 1; i <= n; ++i) {pre[i] = pre[i-1] + a[i]; // a[i]若为int,会自动提升为long long
    }
    

三、二维前缀和:处理矩阵区间和

一维前缀和的思想可扩展到二维场景,用于快速计算矩阵中任意子矩阵的元素和(如查询(x1,y1)(x2,y2)构成的子矩阵和),是图像处理、矩阵分析等领域的常用技巧。

3.1 二维前缀和的定义与推导

设原矩阵为a[1...n][1...m](下标从1开始),二维前缀和数组pre[0...n][0...m]的定义为:

  • pre[i][j]表示以(1,1)为左上角、(i,j)为右下角的子矩阵的所有元素之和。

在这里插入图片描述

递推公式推导:

要计算pre[i][j],需考虑以下四部分:

  1. 左上角子矩阵(1,1)-(i-1,j-1)的和:pre[i-1][j-1]
  2. 左边子矩阵(1,j)-(i-1,j)的和:pre[i-1][j] - pre[i-1][j-1]
  3. 上边子矩阵(i,1)-(i,j-1)的和:pre[i][j-1] - pre[i-1][j-1]
  4. 当前元素a[i][j]

合并后得到递推公式:

pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + a[i][j]

子矩阵和计算:

对于任意子矩阵(x1,y1)-(x2,y2)(左上角为(x1,y1),右下角为(x2,y2)),其和sum的公式为:

sum = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1]

推导逻辑:用pre[x2][y2](大矩阵和)减去“上方无关区域”(pre[x1-1][y2])和“左方无关区域”(pre[x2][y1-1]),但此时“左上角重叠区域”(pre[x1-1][y1-1])被多减了一次,需加回。

3.2 二维前缀和的C++实现

以下代码实现“输入一个n行m列的矩阵,处理q次子矩阵和查询”:

#include <iostream>
#include <vector>
using namespace std;int main() {int n, m, q; // n:行数,m:列数,q:查询次数cin >> n >> m >> q;// 1. 输入原矩阵(下标1开始)vector<vector<int>> a(n + 1, vector<int>(m + 1));for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {cin >> a[i][j];}}// 2. 预处理二维前缀和数组vector<vector<long long>> pre(n + 1, vector<long long>(m + 1, 0));for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + a[i][j];}}// 3. 处理q次查询while (q--) {int x1, y1, x2, y2; // 子矩阵的左上角(x1,y1)和右下角(x2,y2)cin >> x1 >> y1 >> x2 >> y2;// 计算子矩阵和long long sum = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1];cout << "子矩阵(" << x1 << "," << y1 << ")-(" << x2 << "," << y2 << ")的和为:" << sum << endl;}return 0;
}
输入输出示例:
输入:
3 3 2  // 3行3列矩阵,2次查询
1 2 3  // 第1行
4 5 6  // 第2行
7 8 9  // 第3行
1 1 2 2  // 查询子矩阵(1,1)-(2,2)(1+2+4+5=12)
2 3 3 3  // 查询子矩阵(2,3)-(3,3)(6+9=15)输出:
子矩阵(1,1)-(2,2)的和为:12
子矩阵(2,3)-(3,3)的和为:15

四、前缀和的实战应用场景

前缀和并非孤立的技巧,而是许多复杂算法的基础组件,以下列举典型应用场景。

4.1 场景1:统计区间和等于k的子数组个数

问题描述:给定一个整数数组a和整数k,统计所有和为k的连续子数组的个数(LeetCode 560. Subarray Sum Equals K)。

前缀和思路

  • 设前缀和数组为pre,则子数组[i+1, j]的和为pre[j] - pre[i]
  • pre[j] - pre[i] = k,则pre[i] = pre[j] - k
  • 遍历数组时,用哈希表(unordered_map)存储每个pre[i]出现的次数,对于当前pre[j],查询哈希表中pre[j]-k的出现次数,累加至结果。

C++实现代码

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;int subarraySum(vector<int>& nums, int k) {unordered_map<long long, int> preCount; // key:前缀和,value:出现次数preCount[0] = 1; // 初始化:pre[0] = 0出现1次long long pre = 0; // 当前前缀和int res = 0;for (int num : nums) {pre += num; // 更新当前前缀和// 若pre - k存在,说明有pre[i] = pre[j] - k,对应子数组和为kif (preCount.find(pre - k) != preCount.end()) {res += preCount[pre - k];}preCount[pre]++; // 记录当前前缀和的出现次数}return res;
}int main() {vector<int> nums = {1, 1, 1};int k = 2;cout << "和为" << k << "的子数组个数:" << subarraySum(nums, k) << endl; // 输出2return 0;
}

复杂度分析:时间复杂度O(n)(遍历数组一次,哈希表查询/插入为O(1)),空间复杂度O(n)(哈希表存储前缀和)。

4.2 场景2:二维矩阵中的最大子矩阵和

问题描述:给定一个二维整数矩阵,找到一个子矩阵,使其元素和最大(类似“二维版最大子数组和”)。

前缀和思路

  1. 用二维前缀和预处理矩阵,将任意子矩阵和的计算降为O(1)
  2. 固定子矩阵的“上下边界”(如固定第i行到第j行),将每列的和压缩为一个“一维数组”(列和数组);
  3. 对列和数组求“最大子数组和”(Kadane算法),即为“上下边界为i~j”时的最大子矩阵和;
  4. 遍历所有可能的上下边界,取最大值即为最终结果。

核心优势:将二维问题转化为一维问题,时间复杂度从O(n^2m^2)(暴力枚举所有子矩阵)优化为O(n^2m)(n为行数,m为列数)。

4.3 场景3:前缀和与差分的结合

前缀和与差分是“逆运算”关系:差分数组的前缀和是原数组,原数组的前缀和是“二次前缀和”。两者结合可高效解决“多次区间更新+区间查询”的问题(如LeetCode 1109. Corporate Flight Bookings)。

例如,若需对数组a[l, r]区间每次加val(共q次更新),最后查询[x, y]的和:

  1. 用差分数组diff处理区间更新(diff[l] += valdiff[r+1] -= val),更新时间O(1)
  2. diff求前缀和得到更新后的a数组,时间O(n)
  3. a求前缀和,查询[x, y]的和,时间O(1)

总时间复杂度O(n + q),远优于暴力更新的O(q*n)

五、总结与常见误区

5.1 前缀和的核心优势

  • 时间优化:将多次区间和查询的时间从O(n*q)降至O(n + q),是“以空间换时间”的经典案例;
  • 通用性强:可扩展到二维、三维场景,且能与哈希表、Kadane算法等结合解决复杂问题;
  • 实现简单:核心逻辑仅需几行代码,易于理解和调试。

5.2 常见误区与避坑指南

  1. 下标混淆:务必明确原数组和前缀和数组的下标起始位置(0-based或1-based),避免查询时出现l-1越界;
  2. 数据溢出:当原数组元素较大或长度较长时,前缀和需用long long类型(尤其二维前缀和,矩阵元素和更容易溢出);
  3. 空间浪费:若原数组无需保留,可使用“原地前缀和”优化空间;
  4. 不适用于动态修改:前缀和仅适用于“静态数组”(元素不修改),若需频繁修改元素并查询区间和,应使用线段树树状数组

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94859.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94859.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94859.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[n8n] 全文检索(FTS)集成 | Mermaid图表生成

第5章&#xff1a;全文检索(FTS)集成 在前一章中&#xff0c;我们构建了REST API服务作为数据访问入口。 本章将介绍全文检索(FTS)集成&#xff0c;它如同智能搜索引擎&#xff0c;为工作流系统提供高效灵活的检索能力。 核心架构 前文传送&#xff1a; 技术选型 SQLite …

用户模式与内核模式:操作系统的“权限双轨制”

要理解用户模式与内核模式&#xff0c;首先需要明确一个核心概念——进程&#xff08;Process&#xff09;。我们日常用C语言编译生成的.exe文件&#xff0c;本质是“存储在磁盘上的静态程序”&#xff1b;当它被加载到内存并开始运行时&#xff0c;就转化为“动态活动的进程”…

探索 Vertex AI 与 Elasticsearch

作者&#xff1a;来自 Elastic Jhon Guzmn 了解如何将 Vertex AI 与 Elasticsearch 集成来创建 RAG 应用。按照本教程配置一个 Gemini 模型并在 Kibana 的 Playground 中使用它。 更多阅读&#xff1a; Elasticsearch&#xff1a;在 Elastic 中玩转 DeepSeek R1 来实现 RAG …

[新启航]白光干涉仪在微透镜阵列微观 3D 轮廓测量中的应用解析

引言微透镜阵列作为由数百至数千个微米级透镜单元组成的光学元件&#xff0c;在成像系统、光通信、传感器等领域应用广泛&#xff0c;其表面微观 3D 轮廓参数&#xff08;如曲率半径、面型误差、中心厚度等&#xff09;直接影响光学性能。白光干涉仪凭借非接触、高精度、三维成…

MTK Linux DRM分析(十四)- Mediatek KMS实现mtk_drm_drv.c(Part.2)

一、MTK KMS分析 mtk_drm_kms_init 函数分析 mtk_drm_kms_init 是 MediaTek DRM 驱动程序中的一个静态函数(static int mtk_drm_kms_init(struct drm_device *drm)),位于 mtk_drm_drv.c 文件中。该函数的主要作用是初始化 DRM 设备的 Kernel Mode Setting (KMS) 子系统,包…

大模型RAG(Retrieval-Augmented Generation)

RAG检索增强生成 一种结合了检索与生成能力的人工智能技术&#xff0c;主要用于增强大型语言模型在特定任务中的表现。 含义 RAG 将检索系统与生成模型相结合&#xff0c;当接收到一个查询或问题时&#xff0c;模型首先通过检索模块从大规模知识库中寻找与查询相关的信息片段&a…

企业版Idea 无快捷键的启动方式

在没有快捷键的情况下启动 IntelliJ IDEA 企业版&#xff0c;可以通过以下几种方式进行操作&#xff1a; 1. 通过应用程序菜单启动&#xff08;适用于 macOS&#xff09; 在 macOS 系统中&#xff0c;可以打开 Launchpad&#xff0c;在应用程序列表中找到 IntelliJ IDEA&#x…

智慧清洁革命:有鹿机器人如何重塑三大行业未来

作为有鹿智能巡扫机器人&#xff0c;每天清晨当城市还未苏醒&#xff0c;我已悄然完成数万平方米的清洁工作。搭载254TOPS算力的具身智能大脑&#xff0c;我正重新定义保洁、环卫和物业行业的清洁标准。技术赋能&#xff1a;智慧清洁的全面突破我搭载的Master2000通用具身大脑和…

安宝特方案丨AR异地专家远程支持平台,适合:机电运维、应急处置、监造验收

随着车间设备智能化程度的不断提高&#xff0c;其复杂性越来越高&#xff0c;故障维修难度越来越大&#xff0c;严重依赖设备原厂的技术支持和上门服务。但设备厂家受制于地理远近和专业人才数量的限制&#xff0c;服务的及时性和服务质量均很难保证。鉴于市场现有的通信聊天软…

QT应用层项目20250822

01.服务器端代码1.dbhelper.cpp#include "dbhelper.h" #include <iostream> #include <cstring>using std::string; using std::cerr; using std::cout; using std::endl;template <typename T> std::vector<T>& operator<<(std::…

【Linux】Linux基础开发工具从入门到实践

前言&#xff1a;学了Linux的指令&#xff0c;再就是Linux基础开发工具&#xff0c;熟练掌握基础开发工具是提升效率的关键。本文学习Linux的基础开发工具&#xff0c;无论是软件安装、代码编辑&#xff0c;还是编译调试、版本控制&#xff0c;一套顺手的工具链能让你在开发路上…

黑马点评|项目日记(day02)

目录 一. 全局id生成器 1.为什么需要全局id生成器 2.传统方式的缺陷: 3.典型全局 ID 生成方案的设计思路 二.优惠券秒杀-Redis实现全局唯一id 三.优惠券秒杀-添加优惠券 四.优惠券秒杀-实现秒杀下单 五. 一人一单问题 1.单体项目下 1,超卖问题思路分析 2.乐观锁解决问…

shell脚本编程规范与变量

文章目录Shell编程文档整理一、Shell介绍1.1 简介1.2 Shell解释器二、快速入门2.1 编写Shell脚本2.1.1 创建脚本示例2.1.2 赋予执行权限2.2 执行Shell脚本三、Shell程序&#xff1a;变量3.1 语法格式3.2 变量使用3.3 变量类型四、字符串4.1 单引号4.2 双引号4.3 获取字符串长度…

【AGI使用教程】Coze 搭建智能体(1)

欢迎关注【AGI使用教程】 专栏 【AGI使用教程】GPT-OSS 本地部署&#xff08;1&#xff09; 【AGI使用教程】GPT-OSS 本地部署&#xff08;2&#xff09; 【AGI使用教程】Coze 搭建智能体&#xff08;1&#xff09; 【AGI使用教程】Coze 搭建智能体&#xff08;2&#xff09; 【…

(二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵

首先要明确二分查找算法如何实现&#xff0c;是采用左闭右闭还是左闭右开 左闭右闭 第⼀种写法&#xff0c;我们定义 target 是在⼀个在左闭右闭的区间⾥&#xff0c;也就是[left, right] &#xff08;这个很重要⾮常重要&#xff09;。 区间的定义这就决定了⼆分法的代码应…

损失函数,及其优化方法

什么是损失函数&#xff1f;损失函数&#xff0c;也称为代价函数&#xff0c;是一个用来​​衡量机器学习模型预测结果与真实值之间差距​​的函数。损失函数的优化方法有哪些&#xff0c;各自优缺点是什么&#xff0c;他们的应用范围是什么&#xff1f;方法类别代表算法核心思…

pyqt+Python证件号智能校验工具

目录 一、引言 二、GUI界面设计 1.相关提示 2.效果演示 3.界面设计.py 三、主要程序详解 1.导入相关模块 2.初始化设置 3.校验过程 四、总程序代码 一、引言 在数字化转型加速的背景下&#xff0c;证件信息核验已成为金融、政务、安防等领域的刚需。传统人工校验存在…

主流技术栈 NestJS、TypeScript、Node.js版本使用统计

&#x1f4ca; 2024年主流技术栈版本使用统计&#x1f527; TypeScript 采用情况全球采用率: 38.5% 的开发者使用 TypeScript&#xff08;Stack Overflow 2024&#xff09;增长趋势: 从 2017年的 12% 增长到 2024年的 35%&#xff08;JetBrains 调研&#xff09;TypeScript vs …

Techub News 与 TOKENPOST 达成战略合作以推动中韩 Web3 资讯互通

Techub News 消息&#xff0c;香港 Web3 媒体 Techub News 与韩国区块链媒体 TOKENPOST 达成战略合作。TOKENPOST 将开设香港内容板块&#xff0c;由 Techub News 提供本地化行业资讯&#xff1b;同时 Techub News 将推出韩国内容专栏&#xff0c;内容源由 TOKENPOST 支持。这一…

Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(私有区域)

文章目录一、前言1.1 什么是JVM内存结构1.2 JVM内存结构与Java内存模型的区别1.3 为什么面试官爱问JVM内存结构二、JVM运行时数据区总览2.1 运行时数据区域划分2.2 线程私有区域 vs 线程共享区域三、线程私有区域详解3.1 程序计数器&#xff08;PC Register&#xff09;3.1.1 定…