【算法】前缀和

news/2024/5/13 8:43:20



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、一维前缀和
  • 二、二维前缀和
  • 三、寻找数值的中心下标
  • 四、除自身以外数组的乘积
  • 五、和为k的子数组
  • 六、和可被k整除的子数组
  • 七、连续数组
  • 八、矩阵区域和
  • 总结

引言

前缀和,实际上是一种非常简单的动态规划,通过预处理前缀和数组,以空间换时间,提高运行效率。

一、一维前缀和


思路:

  1. 状态表示:dp[i] 为 [1,i] 区间的前缀和(为了方便表示,下标从1开始)
  2. 状态转移方程:dp[i] = dp[i-1] + v[i];
  3. 使用前缀和数组:dp[r] - dp[l-1](表示从l到r的区间和)
int main()
{int n, q, l, r;cin >> n >> q;vector<int> v(n+1);vector<long> dp(n+1);for(int i=1; i<=n; ++i){cin >> v[i];dp[i] = dp[i-1] + v[i];}while(q--){cin >> l >> r;cout << dp[r] - dp[l-1] <<endl;}return 0;
}

二、二维前缀和


思路:

  1. 状态表示:dp[i][j] 为 (1,1) 到 (i, j) 区间的前缀和
  2. 状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1] + vv[i][j] - dp[i-1][j-1];
  3. 使用前缀和数组:dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1]
int main()
{int m, n, q;cin >> m >> n >> q;vector<vector<int>> vv(m+1, vector<int>(n+1));vector<vector<long long>> dp(m+1, vector<long long>(n+1));       for(int i=1; i<m+1; ++i){for(int j=1; j<n+1; ++j){cin >> vv[i][j];dp[i][j] = dp[i-1][j] + dp[i][j-1] + vv[i][j] - dp[i-1][j-1];}}while(q--){int x1, y1, x2, y2;cin >> x1 >> y1 >> x2 >> y2;cout << dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1] << endl;}return 0;
}

三、寻找数值的中心下标


思路:

  1. 创建前缀和数组f 和 后缀和数组g
  2. 状态表示:f[i]代表[0,i-1]区间的前缀和,g[i]代表[i+1,n-1]区间的后缀和
  3. f[i] = f[i-1] + nums[i-1]; g[i] = g[i+1] + nums[i+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;}
};

四、除自身以外数组的乘积


思路:

  1. 创建前缀积数组f 和 后缀积数组g
  2. 状态表示:f[i]代表[0,i-1]区间的前缀积,g[i]代表[i+1,n-1]区间的后缀积
  3. f[i] = f[i-1] * nums[i-1]; g[i] = g[i+1] * nums[i+1];
class Solution
{
public:vector<int> productExceptSelf(vector<int>& nums){int n = nums.size();vector<int> f(n, 1), g(n, 1), ans(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)ans[i] = f[i] * g[i];return ans;}
};

五、和为k的子数组


思路:前缀和 + 哈希表

  1. 状态表示:dp[i] 代表以i为结尾的区间中和为k的子数组个数
  2. 在以i为结尾的区间中,寻找和为dp[i]-k的前缀和,统计个数
  3. 无需创建前缀和数组,只需用sum变量维护即可
  4. 为了快速查找,创建哈希表countMap存储前缀和
  5. 处理边界(sum == k):countMap[0] = 1;
  6. 先统计,再将当前前缀和存入哈希表
class Solution
{
public:int subarraySum(vector<int>& nums, int k){unordered_map<int, int> countMap;countMap[0] = 1;int sum = 0, count = 0;for(auto n : nums){sum += n;if(countMap.count(sum-k)) count += countMap[sum-k];++countMap[sum];}return count;}
};

六、和可被k整除的子数组


思路:前缀和 + 哈希表

  1. 本题与上题思路类似
  2. 同余定理:(a-b)% k == 0 等价于 a%k == b%k
  3. (sum-x)% k == 0 等价于 sum%k == x%k(x为之前的前缀和)
  4. 正负取模统一:(sum % k + k) % k
  5. 创建哈希表countMap存储前缀和的余数
class Solution
{
public:int subarraysDivByK(vector<int>& nums, int k){unordered_map<int, int> countMap;countMap[0] = 1;int sum = 0, count = 0;for(auto n : nums){sum += n;int target = (sum % k + k) % k;if(countMap.count(target)) count += countMap[target];++countMap[target];}return count;}
};

七、连续数组


思路:前缀和 + 哈希表

  1. 将0改成-1,转化为和为0的子数组
  2. 哈希表存储<前缀和,长度>
  3. 处理sum == k的边界情况:countMap[0] = -1;
  4. 哈希表中只存当前值对应长度最短的前缀和(为了求最长的子数组)
class Solution
{
public:int findMaxLength(vector<int>& nums){//转化:和为0的子数组unordered_map<int, int> countMap;//存储<前缀和,长度>countMap[0] = -1;//处理sum == k的边界情况int sum = 0, len = 0, n = nums.size();for(int i=0; i<n; ++i){sum += nums[i] == 0 ? -1 : 1;//将0变成-1,才能转化if(countMap.count(sum)) len = max(len, i-countMap[sum]);else countMap[sum] = i;//存在代表下标更小,不用更新;不存在才要插入}return len;}
};

八、矩阵区域和


思路:

  1. 二维前缀和
  2. 因为dp矩阵下标从1开始,而原始矩阵下标从0开始,所以注意下标映射关系
  3. 为了防止越界,求坐标时用max和min函数与边界比较
class Solution
{
public:int dp[110][110] = {0};vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k){int m = mat.size(), n = mat[0].size();vector<vector<int>> ans(m, vector<int>(n));//预处理前缀和矩阵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];}}//使用前缀和矩阵int x1 = 0, y1 = 0, x2 = 0, y2 = 0;for(int i=0; i<m; ++i){for(int j=0; j<n; ++j){x1 = max(0, i-k) + 1, y1 = max(0, j-k) + 1;x2 = min(m-1, i+k) + 1, y2 = min(n-1, j+k) + 1;ans[i][j] = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];}}return ans;}
};

总结

前缀和,以空间换时间,时间复杂度为O(N),只需常数次遍历即可。

  • 小技巧:前缀和数组下标从1开始,可以忽略很多边界情况~

前缀和的模板不用死记硬背,需要根据题目要求变化,现场推导即可。


真诚点赞,手有余香


http://www.mrgr.cn/p/41551873

相关文章

Java 循环语句

文章目录 Java 循环语句一&#xff0c;for 循环1. for 循环结构2. for 循环案例: 输出5行HelloWord3. for 循环案例: 写出输出的结果 (格式多样性)4. for 循环案例: 遍历100以内的偶数。并获取偶数的个数&#xff0c;获取所有的偶数的和5. for 循环案例: 输出所有的水仙花数6. …

179. 最大数(LeetCode)

文章目录 前言一、题目讲解二、算法原理三、代码编写1.仿函数写法2.lambda表达式 四、验证五.总结 前言 在本篇文章中&#xff0c;我们将会带着大家采用贪心的方法解决LeetCode中最大数这道问题&#xff01;&#xff01;&#xff01; 一、题目讲解 一组非负整数&#xff0c;包…

iZotope RX 10 音频修复和增强工具 mac/win

iZotope RX 10 for Mac是一款出色的音频修复和增强工具&#xff0c;凭借其卓越的音频处理技术&#xff0c;能够轻松应对各种音频问题。 无论是背景噪音、回声还是失真&#xff0c;RX 10都能精准去除&#xff0c;还原清晰纯净的音频。同时&#xff0c;它还提供了丰富的增强工具&…

Linux的磁盘分区,格式化,挂载

1.需要提前添加几个磁盘&#xff0c;以做实验 2.把nvme0n2磁盘用来分区实验 3.分了一个主分区&#xff0c;和一个扩展分区&#xff08;扩展分区是不能使用的&#xff0c;所以又在扩展分区里分了一个逻辑分区&#xff09;分区的大小自己定义 4.格式化分出来的区&#xff0c;这…

abowman小宠物插件-博客园

首先进入abowmab右上角有很多宠物的选项(我选的是仓鼠)然后copy源码 <iframe width="400" height="300" frameborder="0" src="https://cdn.abowman.com/widgets/hamster/hamster.html?up_bgColor=ffffff&up_bodyColor=e6debe&…

AI大模型探索之路-训练篇3:大语言模型全景解读

文章目录 前言一、语言模型发展历程1. 第一阶段&#xff1a;统计语言模型&#xff08;Statistical Language Model, SLM&#xff09;2. 第二阶段&#xff1a;神经语言模型&#xff08;Neural Language Model, NLM&#xff09;3. 第三阶段&#xff1a;预训练语言模型&#xff08…

下载安装git

如何下载安装Git 一、去官网下载git git官网地址:https://git-scm.com/download 选择自己的系统下载PS:官网下载很慢,可以搜清华大学开源软件,选择适合自己的下载下载完成之后点击安装包安装二、开始安装配置 没啥好改的,一直点击“下一步”就好了选择安装的路径其他的一直…

日志分析-apache日志分析

简介 账号密码 root apacherizhi ssh root@IP 1、提交当天访问次数最多的IP,即黑客IP: 2、黑客使用的浏览器指纹是什么,提交指纹的md5: 3、查看index.php页面被访问的次数,提交次数: 4、查看黑客IP访问了多少次,提交次数: 5、查看2023年8月03日8时这一个小时内有多少IP…

jsp servlet 学生信息管理系统

一、角色划分 1、超级管理员 2、学生 二、模块展示 1、登录 2、列表页面【超级管理员展示所有用户信息、学生只展示当前登录用户信息】 3、新增 4、编辑 三、数据库【mysql】 四、运行演示 jsp servlet 学生信息管理系统

C语言操作符和关键字

文章目录 操作符单目操作符sizeof&#xff08;类型&#xff09;强制类型转换 关系操作符、逻辑操作符、条件操作符逗号表达式 常见关键字typedefstaticstatic修饰局部变量static修饰全局变量static修饰函数 register寄存器关键词define定义常量和宏 操作符 单目操作符 C语言中…

机器学习和深度学习-- 李宏毅(笔记与个人理解)Day22

Day 22 Transformer seqence to seqence 有什么用呢&#xff1f; Encoder how Block work 仔细讲讲Residual 的过程&#xff1f; 重构 Decoder - AutoRegressive Mask 由于是文字接龙&#xff0c;所以无法考虑右边的 info 另一种decoder Encoder to Decoder – Cross Attend…

[Vue warn]: Duplicate keys detected: item.id. This may cause an update error.

[Vue warn]: Duplicate keys detected: item.id. This may cause an update error.

vue之生命周期钩子

一、简单理解生命周期 Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程,我们可以通过生命周期钩子函数,在特定的生命周期阶段执行特定的操作。二、常见的生命周期钩子 1. beforeCreate:实例创建之初,此…

网络通信安全

一、网络通信安全基础 TCP/IP协议简介 TCP/IP体系结构、以太网、Internet地址、端口 TCP/IP协议简介如下&#xff1a;&#xff08;from文心一言&#xff09; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/网际协议&#xff0…

29.基础乐理-C大调也会用到黑键?

C大调也会运用到黑键&#xff1f;这个问题是在问&#xff0c;在一首注明为 某某大调 的音乐中&#xff0c;能够出现 某某大调 音阶中没有出现的音吗&#xff1f;比如C大调的音阶是 CDEFGAB&#xff0c;那C大调里 可以出现 升C、升D之类的音吗&#xff1f;再比如 D大调&#xff…

云服务器部署lucky配合frp实现域名访问本地Docker容器

云服务器部署lucky配合frp实现域名访问DX4600 FRP内网穿透可以看我这个帖子:https://www.cnblogs.com/snbg/p/18040720 操作流程(配置流程) 1.购买一个域名和服务器 2.配置云服务器 3.部署lucky实现域名访问 操作步骤(配置步骤) 一、购买一个域名和服务器 1.购买一个域名 …

应用实战 | 别踩白块小游戏,邀请大家来PK挑战~

“踩白块会输”是一个简单的微信小程序游戏&#xff0c;灵感来自当年火热的别踩白块游戏&#xff0c;程序内分成三个模块&#xff1a;手残模式、经典模式和极速模式&#xff0c;分别对应由易到难的三种玩法&#xff0c;可以查看游戏排名。动画效果采用JS实现&#xff0c;小程序…

springboot3整合redis

redis在我们的日常开发中是必不可少的&#xff0c;本次来介绍使用spring boot整合redis实现一些基本的操作&#xff1b; 1、新建一个spring boot项目&#xff0c;并导入相应的依赖&#xff1b; <dependency><groupId>org.springframework.boot</groupId><…

【介绍下IDM的实用功能】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…