算法深度剖析:前缀和

文章目录

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


前言

本章将深度剖析前缀和,以及总结前缀和模板。

前缀和是一种在算法和数据处理中的重要技巧,特别适合解决连续子数组求和的问题。通过构建一个前缀和数组,我们可以快速查询任意连续区间的和,从而在一定程度上优化时间复杂度。

基本原理
前缀和的核心思想是预先计算数组的前缀和,使得区间求和可以在常数时间内完成。假设有一个数组 ( arr ),其前缀和数组定义如下:

  • 设 ( prefix[i] ) 表示从数组起点到位置 ( i ) 的元素之和。
  • 因此,前缀和数组 ( prefix ) 可以定义为:
    [
    prefix[i] = arr[0] + arr[1] + \dots + arr[i]
    ]

计算任意区间和 ( arr[l] + arr[l+1] + \dots + arr[r] ) 可以通过前缀和快速得到:
[
arr[l] + arr[l+1] + \dots + arr[r] = prefix[r] - prefix[l-1]
]
其中, ( prefix[r] ) 是从 ( arr[0] ) 到 ( arr[r] ) 的和,减去从 ( arr[0] ) 到 ( arr[l-1] ) 的和就得到了区间 ( [l, r] ) 的和。

例子
假设有数组 ( arr = [1, 2, 3, 4, 5] ),构建前缀和数组 ( prefix ) 如下:

  • ( prefix[0] = 1 )
  • ( prefix[1] = 1 + 2 = 3 )
  • ( prefix[2] = 1 + 2 + 3 = 6 )
  • ( prefix[3] = 1 + 2 + 3 + 4 = 10 )
  • ( prefix[4] = 1 + 2 + 3 + 4 + 5 = 15 )

那么,求区间和 ( arr[1] + arr[2] + arr[3] ) 就可以通过前缀和数组计算:
[
arr[1] + arr[2] + arr[3] = prefix[3] - prefix[0] = 10 - 1 = 9
]

时间复杂度

  • 构建前缀和数组的时间复杂度为 ( O(n) ),其中 ( n ) 是数组的长度。
  • 一旦构建好前缀和数组,查询任意区间的和的时间复杂度为 ( O(1) )。

前缀和技术通常用于快速解决子数组求和、二维区域求和等问题。

在这里插入图片描述


一、一维前缀和模板

【模板】前缀和
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
#include<vector>
typedef long long LL;int n,q;int main()
{cin >> n >> q;vector<LL> arr(n + 1);for (int i = 1; i <= n; i++){cin >> arr[i];}//定义前缀和数组vector<LL> dp(n + 1);for (int i = 1; i <= n; i++){dp[i] = dp[i - 1] + arr[i];}//使用前缀和数组while (q--){LL l, r;cin >> l >> r;cout << dp[r] - dp[l - 1] << endl;}return 0;
}

二、二维前缀和模板

【模板】二维前缀和

在这里插入图片描述

在这里插入图片描述

#include <iostream>
using namespace std;#include<vector>
typedef long long LL;int main()
{int n, m, q;cin >> n >> m >> q;//初始化原始数据vector<vector<LL>> arr(n + 1, vector<LL> (m + 1));for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){cin >> arr[i][j];}}//定义前缀和数组vector<vector<LL>> dp(n + 1, vector<LL> (m + 1));for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1];}}//使用前缀和数组while (q--){LL 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;
}

三、寻找数组的中心下标

寻找数组的中心下标

在这里插入图片描述
在这里插入图片描述

算法思路:

根据中心下标的定义,除中心下标元素外,该元素左边的「前缀和」应等于右边的「后缀和」。

  • 因此,可以先预处理两个数组,一个表示前缀和,另一个表示后缀和
  • 然后,用一个 for 循环枚举可能的中心下标,判断每个位置的前缀和和后缀和是否相等,如果相等,则返回该下标。
class Solution {
public:int pivotIndex(vector<int>& nums) {int n = nums.size();//构建前缀和vector<int> dp_first(n);dp_first[0] = 0;for (int i = 1; i < n; i++){dp_first[i] = dp_first[i - 1] + nums[i - 1];}//构建后缀和vector<int> dp_end(n);dp_end[n - 1] = 0;for (int i = n - 2; i >= 0; i--){dp_end[i] = dp_end[i + 1] + nums[i + 1];}//使用前缀和for (int i = 0; i < n; i++){if (dp_first[i] == dp_end[i])return i;}return -1;}
};

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

除自身以外数组的乘积

在这里插入图片描述

算法思路:

题目要求不能使用除法,并要求在 O ( N ) O(N) O(N) 时间复杂度内完成,排除了暴力解法和计算数组乘积后除以单个元素的方法。

分析可知,每个位置的最终结果 ret[i] 可以分为两部分:

  1. 前缀积部分nums[0] * nums[1] * ... * nums[i - 1]
  2. 后缀积部分nums[i + 1] * nums[i + 2] * ... * nums[n - 1]

可以利用前缀和的思想,定义两个数组 postsuf,分别存储两部分信息:

  1. post:表示 i 位置之前所有元素的前缀乘积,即 [0, i - 1] 区间的乘积。
  2. suf:表示 i 位置之后所有元素的后缀乘积,即 [i + 1, n - 1] 区间的乘积。

最后,根据 postsuf 计算出每个位置的最终结果。
在这里插入图片描述

class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {int n = nums.size();//构建前缀积vector<int> dp_first(n);dp_first[0] = 1;for (int i = 1; i < n; i++){dp_first[i] = dp_first[i - 1] * nums[i - 1];}//构建后缀积vector<int> dp_end(n);dp_end[n - 1] = 1;for (int i = n - 2; i >= 0; i--){dp_end[i] = dp_end[i + 1] * nums[i + 1];}//使用前后缀积vector<int> answer(n);for (int i = 0; i < n; i++){answer[i] = dp_first[i] * dp_end[i];}return answer;}
};

五、和为 K 的子数组

和为 K 的子数组
在这里插入图片描述
(将前缀和存入哈希表):

算法思路:

i 为数组中的任意位置,sum[i] 表示 [0, i] 区间内所有元素的和。

  • 我们需要找到“以 i 为结尾且和为 k 的子数组”,这等价于找出所有可能的起始位置 x1, x2, x3...,使得 [x, i] 区间的和为 k
  • 此时,[0, x] 区间的和应为 sum[i] - k

因此,问题转化为:

  • 找出 [0, i - 1] 区间内有多少前缀和等于 sum[i] - k

无需真正初始化前缀和数组,因为我们只关注 i 位置之前,前缀和为 sum[i] - k 的次数。我们可以使用一个哈希表,在计算当前位置的前缀和时,同时记录每个前缀和出现的次数。

在这里插入图片描述

class Solution {
public:int subarraySum(vector<int>& nums, int k) {// 哈希表模拟前缀和数组unordered_map<int, int> hash;hash[0] = 1;//使用前缀和数组int sum = 0, ret = 0;for (auto e : nums){sum += e;if (hash.count(sum - k)) ret += hash[sum - k];hash[sum]++;}return ret;}
}; 

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

和可被 K 整除的子数组
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本题需要的前置知识:

  • 同余定理
    (a - b) % n == 0,则 a % n == b % n。也就是说,如果两个数之差能被 n 整除,那么这两个数对 n 取模的结果相同。
    例如,(26 - 2) % 12 == 0,所以 26 % 12 == 2 % 12 == 2

  • C++ 中负数取模结果的处理

    • 在 C++ 中,负数取模的结果会保留负号,例如 -1 % 3 = -1
    • 为防止负数结果影响,常用 (a % n + n) % n 确保结果为正,例如:-1 % 3 = (-1 % 3 + 3) % 3 = 2

算法思路:

此题与 LeetCode 560 题“和为 K 的子数组”思路类似。

i 为数组中的任意位置,sum[i] 表示 [0, i] 区间内的和。

  • 要找出“以 i 为结尾、和可被 k 整除的子数组”,需要找到所有起点 x1, x2, x3... 使得 [x, i] 的和能被 k 整除。
  • 假设 [0, x - 1] 的和为 a[0, i] 的和为 b,则有 (b - a) % k == 0
  • 根据同余定理,[0, x - 1] 区间和 [0, i] 区间的前缀和同余。因此问题变成:
    • 找到 [0, i - 1] 中前缀和的余数等于 sum[i] % k 的个数。

无需初始化前缀和数组,只需用一个哈希表记录每种前缀和的出现次数,同时计算当前位置的前缀和。

class Solution {
public:int subarraysDivByK(vector<int>& nums, int k) {// 哈希表模拟前缀和数组unordered_map<int,int> hash;hash[0] = 1;//使用前缀和数组int sum = 0, ret = 0;for (auto e : nums){sum += e;int r = (sum % k + k) % k;if (hash.count(r)) ret += hash[r];hash[r]++;}return ret;}
};

七、连续数组

连续数组

在这里插入图片描述

算法思路:

稍作转换,这道题可以化为经典问题:

  • 本题需要找一个连续区间,使得 0 和 1 出现的次数相同。
  • 将 0 视为 -1,1 视为 1,问题就转化为找一个区间,使其和等于 0。

这样问题与 LeetCode 560 题“和为 K 的子数组”思路相似。

i 为数组中任意位置,用 sum[i] 表示 [0, i] 区间内所有元素的和。我们希望找到最大长度的“以 i 为结尾、和为 0 的子数组”,这需要找到从左至右第一个位置 x1 使得 [x1, i] 的和为 0。此时 [0, x1 - 1] 区间的和等于 sum[i]。因此问题变成:

  • 找到 [0, i - 1] 区间内首次出现 sum[i] 的位置即可。

我们无需真正初始化一个前缀和数组,因为只关心 i 位置之前,首次出现等于 sum[i] 的前缀和位置。只需一个哈希表,在计算当前位置前缀和的同时,记录该前缀和的首次出现位置。
在这里插入图片描述

class Solution {
public:int findMaxLength(vector<int>& nums) {// 哈希表模拟前缀和数组unordered_map<int, int> hash;hash[0] = -1; // 使用前缀和数组int sum = 0, ret = 0;for (int i = 0; i < nums.size(); i++){sum += nums[i] == 0 ? -1 : 1;if (hash.count(sum)) ret = max(ret, i - hash[sum]);else hash[sum] = i;}    return ret;}
};

八、矩阵区域和

矩阵区域和
在这里插入图片描述

算法思路:

这道题主要是二维前缀和的基本应用,关键在于填写结果矩阵时,找到原矩阵对应区域的「左上角」和「右下角」坐标(建议画图理解)。

  • 左上角坐标x1 = i - k,y1 = j - k,为了不超出矩阵范围,需要对 0 取 max,修正后的坐标为:x1 = max(0, i - k),y1 = max(0, j - k)
  • 右下角坐标x2 = i + k,y2 = j + k,同理,为避免超出矩阵范围,需要对 m - 1n - 1min,修正后的坐标为:x2 = min(m - 1, i + k),y2 = min(n - 1, j + k)

最后将修正后的坐标代入二维前缀和的计算公式即可(注意下标的映射关系)。
在这里插入图片描述

class Solution {
public:vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {// 构建二维前缀和int n = mat.size(), m = mat[0].size();vector<vector<int>> dp(n + 1, vector<int> (m + 1));for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + mat[i - 1][j - 1] - dp[i - 1][j - 1];}}   //使用二维前缀和vector<vector<int>> answer(n, vector<int> (m));for (int i = 0; i < n; i++){for (int j = 0; j < m; j++){int a, b, c, d;a = i - k < 0 ? 1 : i - k + 1;b = j - k < 0 ? 1 : j - k + 1;c = i + k >= n ? n : i + k + 1;d = j + k >= m ? m : j + k + 1;answer[i][j] = dp[c][d] - dp[c][b - 1] - dp[a - 1][d] + dp[a - 1][b - 1];}}return answer;}
};

在这里插入图片描述

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

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

相关文章

leetcode双指针题目总结

文章目录 283. 移动零题目描述解题 11. 盛最多水的容器题目描述解法解释时间复杂度 15. 三数之和题目描述解法思路解释优势 42. 接雨水题目描述解答具体思路 283. 移动零 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非…

Android——显式/隐式Intent

概述 在Android中&#xff0c;Intent是各个组件之间信息通信的桥梁&#xff0c;它用于Android各组件的通信。 Intent 的组成部分 一、显式 Intent 第一种方式 Intent intent new Intent(this, ActFinishActivity.class);startActivity(intent);第二种方式 Intent intent …

时序预测 | Matlab基于TSA-LSTM-Attention被囊群优化算法优化长短期记忆网络融合注意力机制多变量多步时间序列预测

时序预测 | Matlab基于TSA-LSTM-Attention多变量多步预测 目录 时序预测 | Matlab基于TSA-LSTM-Attention多变量多步预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Matlab基于TSA-LSTM-Attention被囊群优化算法优化长短期记忆网络融合注意力机制多变量多…

PAT甲级-1041 Be Unique

题目 题目大意 从一组数字中选出第一个唯一出现的数&#xff0c;输出该数。如果没有&#xff0c;则输出None。 思路 哈希的思想&#xff0c;将数值作为索引&#xff0c;对应该数值出现的次数&#xff0c;然后遍历数组即可。 注意第一个数字是指数字的个数&#xff0c;不是数…

LLM 使用 Elastic 实现可观察性:Azure OpenAI (二)

作者&#xff1a;来自 Elastic Muthukumar Paramasivam•Lalit Satapathy 我们为 Azure OpenAI GA 包添加了更多功能&#xff0c;现在提供提示和响应监控、PTU 部署性能跟踪和计费洞察&#xff01; 我们最近宣布了 Azure OpenAI 集成的 GA。你可以在我们之前的博客 LLM 可观察性…

Python画图3个小案例之“一起看流星雨”、“爱心跳动”、“烟花绚丽”

源码如下&#xff1a; import turtle # 导入turtle库&#xff0c;用于图形绘制 import random # 导入random库&#xff0c;生成随机数 import math # 导入math库&#xff0c;进行数学计算turtle.setup(1.0, 1.0) # 设置窗口大小为屏幕大小 turtle.title("流星雨动画&…

二十九、Python基础语法(继承-上)

一、概念介绍 继承&#xff1a;继承描述的是类与类之间的关系&#xff0c;集成之后子类对象可以直接使用父类中定义的方法的属性&#xff0c;可以减少代码冗余&#xff0c;提高编码效率。 二、继承语法 三、继承例子 # 定义一个父类 Animal class Animal:def __init__(self,…

‌频率和波长之间存在反比关系‌

‌频率和波长之间存在反比关系‌。根据波速公式vλf&#xff0c;在同种介质中&#xff0c;波的传播速度相同&#xff0c;因此波长和频率的乘积不变&#xff0c;即λv/f。这意味着频率越高&#xff0c;波长越短&#xff1b;频率越低&#xff0c;波长越长。 ‌频率和波数的转换公…

Rust 力扣 - 238. 除自身以外数组的乘积

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题主要有个关键点&#xff0c;就是元素能取0&#xff0c;然后我们分类讨论元素为0的数量 如果数组中存在至少两个元素为0&#xff0c;则每个元素的除自身以外的乘积为0如果数组中仅存在一个0&#xff0c;则为…

我们来学mysql -- 连接(原理版)

我们来学mysql -- 连接 题记两张表驱动表 题记 回到初学者的视角&#xff0c;navicat或命令窗口&#xff0c;呈现一行行数据&#xff0c;类比为excel工作薄更是深入人心通过join将多表的记录关联起来&#xff0c;这似乎也没啥问题只是好像是那么回事&#xff0c;又…似乎有想说…

Ubuntu 22.04安装部署

一、部署环境 表 1‑1 环境服务版本号系统Ubuntu22.04 server lts运行环境1JDK1.8前端WEBNginx1.8数据库postgresqlpostgresql13postgis3.1pgrouting3.1消息队列rabbitmq3.X(3.0以上)运行环境2erlang23.3.3.1 二、安装系统 2.1安装 1.安装方式&#xff0c;选第一条。 2.选择…

红队-windows基础篇

声明 通过学习 泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频,做出的文章如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 一.Windows&linux介绍 微软windows操作系统&#xff0c;…

QT 从ttf文件中读取图标

最近在做项目时&#xff0c;遇到需要显示一些特殊字符的需求&#xff0c;这些特殊字符无法从键盘敲出来&#xff0c;于是乎&#xff0c;发现可以从字体库文件ttf中读取显示。 参考博客&#xff1a;QT 图标字体类IconHelper封装支持Font Awesome 5-CSDN博客 该博客封装的很不错…

AnaTraf | 探秘计算机网络:网络流量分析与 TCP 标志位解析

AnaTraf 网络性能监控系统NPM | 全流量回溯分析 | 网络故障排除工具AnaTraf网络流量分析仪是一款基于全流量&#xff0c;能够实时监控网络流量和历史流量回溯分析的网络性能监控与诊断系统&#xff08;NPMD&#xff09;。通过对网络各个关键节点的监测&#xff0c;收集网络性能…

瑞格智慧心理服务平台 NPreenSMSList.asmx SQL注入漏洞复现

0x01 产品简介 瑞格智慧心理服务平台是一家致力于提供个性化心理健康支持的平台。通过先进的AI技术和专业心理学家团队,为用户提供定制化的心理评估和个性化的心理咨询服务。平台注重隐私保护和数据安全,用户可以安全、便捷地接受在线咨询和心理指导,帮助他们理解和应对各种…

MATLAB口罩检测

在当今疫情严峻的背景下&#xff0c;口罩成为了人们必备的防护用品。然而&#xff0c;市面上却出现了大量假冒伪劣口罩。为了帮助大家准确辨别真假口罩&#xff0c;小编将为大家介绍一种基于MATLAB的口罩检测方法。 1.口罩检测原理 通过图像处理技术&#xff0c;借助MATLAB强大…

【刷题13】链表专题

目录 一、两数相加二、两两交换链表的节点三、重排链表四、合并k个升序链表五、k个一组翻转链表 一、两数相加 题目&#xff1a; 思路&#xff1a; 注意整数是逆序存储的&#xff0c;结果要按照题目的要求用链表连接起来遍历l1的cur1&#xff0c;遍历l2的cur2&#xff0c;和…

消息队列面试——打破沙锅问到底

消息队列的面试连环炮 前言 你用过消息队列么&#xff1f;说说你们项目里是怎么用消息队列的&#xff1f; 我们有一个订单系统&#xff0c;订单系统会每次下一个新订单的时候&#xff0c;就会发送一条消息到ActiveMQ里面去&#xff0c;后台有一个库存系统&#xff0c;负责获取…

Linux 下执行定时任务之 Systemd Timers

不知道 ECS 因为什么缘故&#xff0c;上面安装的 MySQL 服务老是不定期挂掉&#xff0c;本来想通过 Linux 得 Cron 配置个半小时的定时检测任务&#xff0c;结果一直没有执行&#xff0c;因此又尝试使用了 Systemd Timers 进行了重新配置&#xff0c;简要做个记录。 Systemd Ti…

yocto中如何来安装systemd服务

在 Yocto 项目中安装 systemd 服务可以按照以下步骤进行&#xff1a; 1. 创建服务单元文件&#xff08;.service 文件&#xff09; 在 Recipe 中处理&#xff1a;在 Yocto 中&#xff0c;为了将服务单元文件安装到目标系统&#xff0c;首先需要在软件包的 Recipe&#xff08;…