【算法】前缀和



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的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开始,可以忽略很多边界情况~

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


真诚点赞,手有余香

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/573271.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Github】sync fork后,意外关闭之前提交分支的pr申请 + 找回被关闭的pr请求分支中的文件

【Github】sync fork后&#xff0c;意外关闭之前提交分支的pr申请 找回被关闭的pr请求分支中的文件 写在最前面原因解析提交pr&#xff0c;pr是什么&#xff1f;rebase 或者 merge 命令 找到分支中被删除的文件找到被关闭的提交请求pr方法1&#xff1a;在公共仓库被关闭的pr中…

LeetCode in Python 69. Sqrt(x) (x的平方根)

求x的平方根&#xff0c;第一想法可能是遍历0&#xff5e;x&#xff0c;求其平方&#xff0c;找到或且但其时间复杂度为O(n)&#xff0c;或是想到遍历0&#xff5e;M即可&#xff0c;其中M x // 2&#xff0c;将时间复杂度降至O()。本文利用二分思想&#xff0c;给出一种时间复…

博睿数据亮相GOPS全球运维大会,Bonree ONE 2024春季正式版发布!

2024年4月25日&#xff0c;博睿数据 Bonree ONE 2024 春季正式版焕新发布。同时&#xff0c;博睿数据AIOps首席专家兼产品总监贺安辉携核心产品新一代一体化智能可观测平台 Bonree ONE 亮相第二十二届 GOPS 全球运维大会深圳站。 Bonree ONE 2024 春季版产品重点升级数据采集、…

网上打印资料多少钱一张?网上打印价格是多少?

在数字化时代&#xff0c;网上打印服务正逐渐成为一种便捷、高效的打印解决方案。对于许多需要打印资料的用户来说&#xff0c;了解网上打印的价格和服务质量至关重要。那么&#xff0c;网上打印资料到底多少钱一张&#xff1f;网上打印价格又是如何呢&#xff1f;今天&#xf…

视频号下载小程序:轻松获取视频号视频

在数字化时代&#xff0c;短视频已成为人们日常生活中不可或缺的一部分。为了满足用户随时随地观看视频的需求&#xff0c;视频号小程序应运而生。本文将详细介绍视频号小程序的下载方法、功能特点以及使用技巧&#xff0c;帮助您更好地享受短视频带来的乐趣。 一、视频号小程…

C++ 之 string类 详细讲解

喜欢的人有点难追怎么办 那就直接拉黑 七个女生在一起是七仙女&#xff0c;那七个男生在一起是什么&#xff1f; 葫芦七兄弟 目录 一、为什么要学习string类 二、标准库中的string类 1.string类 2.string类的常用接口说明 2.1 string类对象的常见构造 2.2 string类对…

Vivado-OOC

OOC⇒Out-of-Context 在Vivado中&#xff0c;对于顶层设计&#xff0c;vivado使用自顶向下的全局&#xff08;global&#xff09;综合&#xff0c;将顶层文件下的所有模块都进行综合&#xff0c;但是在实际设计过程中&#xff0c;顶层设计会被多次修改和综合&#xff0c;但是有…

AI语音侵权第一案:配音演员获赔25万元,如何保护你的声音资产?

会议之眼 快讯 近日&#xff0c;北京互联网法院对全国首例AI声音侵权案作出一审宣判&#xff0c;引发了社会对AI技术与个人权益保护关系的广泛讨论。 原告殷某&#xff0c;一名配音师&#xff0c;发现自己的声音被AI化后在“魔音工坊”APP上出售&#xff0c;遂将运营主体等五…

Linux的学习之路:20、进程信号(2)

摘要 本章讲一下进程信号的阻塞信号和捕捉信号和可重入函数 目录 摘要 一、阻塞信号 1、阻塞信号 2、信号集操作函数 二、捕捉信号 1、内核如何实现信号的捕捉 2、代码实演 三、可重入函数 一、阻塞信号 1、阻塞信号 实际执行信号的处理动作称为信号递达(Delivery) …

MyBatis源码之前言—JDBC编码存在的问题和Mybatis的介绍

MyBatis源码之前言—JDBC编码存在的问题和Mybatis的介绍 为了方便操作&#xff0c;我们在sjdwz_test数据库下建立一张表&#xff1a; CREATE TABLE t_student (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,name varchar(255) DEFAULT NULL COMMENT 名字,age int(255…

Web端Webrtc,SIP,RTSP/RTMP,硬件端,MCU/SFU融合视频会议系统方案分析

Web端视频融合&#xff0c;会议互通已经是视频会议应用的大趋势&#xff0c;一是目前企业有大量的老视频会议硬件设&#xff0c;二新业务又需要Web端支持视频会议监控直播需求&#xff0c;迫切需要一个融合对接的方案&#xff0c;即能把老的设备用起来&#xff0c;又能对接新的…

【每日刷题】Day22

【每日刷题】Day22 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1669. 合并两个链表 - 力扣&#xff08;LeetCode&#xff09; 2. 11. 盛最多水的容器 - 力扣&#…

分类算法——ROC曲线与AUC指标(九)

知道TPR与FPR TPRTP/(TP FN) 所有真实类别为1的样本中&#xff0c;预测类别为1的比例 FPR FP/(FP TN) 所有真实类别为0的样本中&#xff0c;预测类别为1的比例 ROC曲线 ROC曲线的横轴就是FPRate&#xff0c;纵轴就是TPRate&#xff0c;当二者相等时&#xff0c;表示的意义…

Linux 内核设备树 ranges属性

今天有人问了我一下ranges属性&#xff0c;找了相关资料确认后&#xff0c;记录一下&#xff1a; 参考资料链接&#xff1a;让你完全理解linux内核设备树ranges属性地址转换 - vkang - 博客园 (cnblogs.com) ranges属性定义如下&#xff1a; ranges < local_address pa…

webpack面试题(持续汇总ing。。。)

webpack的编译过程 初始化 此阶段&#xff0c;webpack会将CLI参数、配置文件、默认配置进行融合&#xff0c;形成一个最终的配置对象。对配置的处理过程是依托一个第三方库 yargs 完成的。此阶段相对比较简单&#xff0c;主要是为接下来的编译阶段做必要的准备目前&#xff0c;…

三数之和 ---- 双指针

题目链接 题目: 分析: 解法一: 暴力解法, 将所有的三元组都算出来看是否为0, 题目要求去重操作, 所以我们可以使用set去重解法二: 因为我们知道当计算两数之和时, 我们使用的方法是将数组排序,然后利用"双指针"那么同理, 计算三个数之和: 1. 排序2. 固定一个数a, …

数据库管理-第176期 浅析代码团队建设(20240425)

数据库管理176期 2024-04-25 数据库管理-第176期 浅析代码团队建设&#xff08;20240425&#xff09;1 国内现状2 需求管控3 竞争与迭代总结 数据库管理-第176期 浅析代码团队建设&#xff08;20240425&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文&#xff09…

安卓Activity的setContentView()流程分析

目录 前言一、Activity的视图加载过程1.1 视图结构1.2 流程分析1.2.1 Activity.java -->setContentView()1.2.2 Activity.java -->getWindow()1.2.3 PhoneWindow.java -->setContentView()1.2.4 PhoneWindow.java --->installDecor()1.2.4.1 PhoneWindow.java ---&…

Yolov5 export.py实现onnx模型的导出

查了很多资料&#xff0c;很多用python代码写的&#xff0c;只需要这个库那个库的&#xff0c;最后都没成功。 不如直接使用Yolov5里面的 export.py实现模型的转换。 一&#xff1a;安装依赖 因为yolov5里面的requirments.txt是将这些转换模型的都注释掉了 所以需要解除注释…

SpringCloud alibaba整合OpenFeign

目录 一、为什么使用OpenFeign 二、准备两个服务 三、最简单使用- 返回字符串 ①引入openfeign依赖 ②调用端在启动类上添加EnableFeignClients注解 ③在被调用端写一个简单的接口 ④在调用端新建一个service类 专门用于远程调用 ​编辑 ⑤ 在调用端写一个conteoller …