【0基础学算法】前缀和 (超详细讲解+私人笔记+源码)

简介: 哈喽大家好,很长时间忘记更新咱们的学算法系列文章了,今天我们终于迎来了我们零基础学算法的第四期内容,那就是前缀和,前缀和其实算是一个偏数学概念,所以我相信你只要了解了这方面知识,就肯定可以去写出正确的代码的,下面我们也废话少说直接进入我们的讲解阶段。

什么是前缀和


假定给我们一个数组,前缀和就是该元素前所以元素和。也就是如果我们设定一个数组s为前缀和数组,那么s[3]就是我们原数组前三个元素之和,这就是我们的前缀和。


我们为什么要去学习前缀和


有很多人疑惑,我们为什么要学习前缀和,在这里我们之所以学习前缀和的原因是因为其可以有效的减少时间复杂度。


如果我们要求一段区间的和,那么我们用普通数组要从第一个加到最后一个循环一边,但是如果我们知道该数组前缀和之后,我们就只需要去让其末元素前缀和减去初元素前的前缀和就可以了。


就比如我们求下标3-10的数组和,那么我们使用前缀和时就只需要去让是s[10]-s[2] 就可以了。


5.png


这样就可以有效的降低我们去计算时的复杂度,这就是为什么我们要去学习前缀和。所以我们下面就要去了解一下前缀和的实现过程。


前缀和实现


这里我们主要是要解决两个问题:


1.如何求s[i] ?


2.S[i]的作用 ?


如何求s[i]

我们在这里将用递推的方法去具体的去求s[i] ,我们在前面已经了解了前缀和的定义,那么我们从头开始求前缀和,之后的前缀和就是其前一个前缀和加上当前元素了。


也就是:


S[i] = S[i-1] + a[i] ;

这样我们循环下来,或者说是我们在输入数组的时候就可以顺便的求出我们的前缀和数组。


当然在这里我们统一的将下表设置为从1开始,具体是要考虑到我们的边界问题,也就是S[1]的求法问题,为了保证我们循环的统一性,我们要将S[0]设置为0,所以我们索性就将下标从1开始设置起,这样也有利于我们后面的初始化。


S[i]的作用

我们前面也提到过,S[i]的作用就是可以快速求出一段区间数的合。


也可以表示为:


S[r] - S[l-1] = a[l] + ... + a[r] ;

利用这个特性可以求一个区间的区间和 。


模板

在这里我们将差分的模板分成两类:也就是一维前缀和和二位前缀和。


一维前缀和

一维前缀和也就是我们前面举例子的时候所提到的,也就是针对一维数组所作的前缀和,那么我们通过前面的了解肯定理解了一维前缀和,那么我们就直接放出一维前缀和的代码模板:


S[i] = a[1] + a[2] + ... a[i]

a[l] + ... + a[r] = S[r] - S[l - 1]

这就是我们学习一维前缀和的核心代码串。

6.png





二维前缀和

对于二维前缀和我们在举例子的时候没有举这个方面的例子,主要是因为二维前置和要相对于一维前缀和更复杂一点,对应的运算也要复杂一点,所以二维前缀和在这里我们为其讲解。


首先我们给出一个二维数组:

7.png



如果我们相求(4,4)的前缀和的话,我们就需要求出这块部分的数值。


8.png



我们想要求这块面积需要怎么求呢?


公式是:    s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1]


那我们来看一下:


s[4][3]:

9.png


s[3][4]:

10.png

这时我们会发现,如果我们这两个相加的话,我们会相加重叠一部分,那么这个时候我们就需要将中间那一部分减去。

11.png



这里我们不难看出,重叠的相加部分就是我们的s[i-1][j-1] ;


这里可能会有人提出疑问,是不是还有一块没加上,但是为什么公式中体现的是s[i][j],这里我们对于二维数组初始化时,不需要建立新的数组,我们只需要使用原数组,将原数组填入其中即可,所以这里的s[i][j]就是当前那一格的大小,在我们更新后,他才成为了起二维前缀和。


这里我们了解了二维数组的初始化之后就要去计算一段区间的二维前缀和,其计算方法与初始化相似,公式:S = S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1] 。


这里就不做具体的讲解了,如果还不太清楚的话,可以按上面的方法去试着推理一下就可以了,其原理是相同的,到代码实现的时候这两个我们也是可以使用一个模板的,因为我们前面的初始化,是相同点,也就是点相同是所围成矩阵值,那就是那一格的值,到后面我们就是两个不同的点所围成的值,就是传入两个不同的点就可以了。

12.png

代码模板:


S[i, j] = 第i行j列格子左上部分所有元素的和

以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:

S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

到这里我们也就学会了前缀和,那么我们下面来写几个题目来了解一下吧。


题目

第一题

输入一个长度为 n 的整数序列。


接下来再输入 m 个询问,每个询问输入一对 l,r。


对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。


输入格式


第一行包含两个整数 n 和 m。


第二行包含 n 个整数,表示整数数列。


接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。


输出格式


共 m 行,每行输出一个询问的结果。


数据范围


1≤l≤r≤n

1≤n,m≤100000

−1000≤数列中元素的值≤1000


输入样例:


5 3

2 1 3 6 4

1 2

1 3

2 4

输出样例:


3

6

10

讲解


这道题目就是单纯的考察了我们一维前缀和的使用,我们在前面已经讲过了计算区间和的时候使用前缀和数组的公式。


那么首先我们要对前缀和数组进行初始化,具体初始化方式就是从前往后逐个初始化:

for(int i=1; i<=n; i++) s[i] = s[i-1] + a[i] ;

之后,我们再利用前缀和数组去计算我们的区间和:


for(int i=1; i<=m; i++)
  {
  cin >> l >> r ;
  cout << s[r]-s[l-1] << endl ;        //输出区间和
  }

我们要注意在初始化的时候下标和边界,注意到这两点这个题也就会变的不那么容易出错了。


AC


#include<iostream>
using namespace std ;
const int N = 100010 ;
int n, m ;
int l, r ;
int a[N] , s[N] ;
int main()
{
  cin >> n >> m ;
  for(int i=1; i<=n; i++) cin >> a[i] ;
  for(int i=1; i<=n; i++) s[i] = s[i-1] + a[i] ;    //计算前缀和
  for(int i=1; i<=m; i++)
  {
  cin >> l >> r ;
  cout << s[r]-s[l-1] << endl ;        //输出区间和
  }
  return 0 ;
}


第二题

输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2表示一个子矩阵的左上角坐标和右下角坐标。


对于每个询问输出子矩阵中所有数的和。


输入格式


第一行包含三个整数 n,m,q。


接下来 n 行,每行包含 m 个整数,表示整数矩阵。


接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。


输出格式


共 q 行,每行输出一个询问的结果。


数据范围


1≤n,m≤1000,

1≤q≤200000,

1≤x1≤x2≤n,

1≤y1≤y2≤m,

−1000≤矩阵内元素的值≤1000


输入样例:


3 4 3

1 7 2 4

3 6 2 8

2 1 2 3

1 1 2 2

2 1 3 4

1 3 3 4

输出样例:


17

27

21

讲解


这个题目就正好可以考察我们二维前缀和的学习情况,那么我们已经在前面讲解了二维前缀和的计算方式,那么我们在这里就将其的实现细化一下。


首先我们要初始化一下二位前缀和


for(int i=1; i<=n; i++)
  {
  for(int j=1; j<=m; j++)
  {
    s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1] ;
  }
  }

在这里我们还是要注意到边界问题,也就是0的问题,因为我们在计算S[1]的时候要用到S[0]所以我们在这里就要让S[0] = 0 ;


之后我们在这里只用到了一个数组,也就是我们数组在更新前是二维数组单个元素,更细后的就编变成了其前缀和元素了。


之后我们要去计算其区间和,具体方法在前面也讲到过,代码实现就如下:

cin >> x1 >> y1 >> x2 >> y2 ;
  cout << s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1] << endl ;    //计算和


AC


#include<iostream>
using namespace std ;
const int N = 1010 ;
int n, m , q ;
int x1, x2, y1, y2 ;
int s[N][N] ;
int main()
{
  cin >> n >> m >> q ;
  for(int i=1; i<=n; i++)
  {
  for(int j=1; j<=m; j++)
  {
    cin >> s[i][j] ;
  }
  }
  for(int i=1; i<=n; i++)
  {
  for(int j=1; j<=m; j++)
  {
    s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1] ;
  }
  }
  while(q--)
  {
  cin >> x1 >> y1 >> x2 >> y2 ;
  cout << s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1] << endl ;    //计算和
  }
  return 0 ;
}


好啦,到这里我们今天的讲解内容也就结束了,也希望你可以学会这个知识,加油我们每天进步一点,到时候回头看来,将是一大步飞跃。


相关文章
|
1月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
46 0
|
1月前
|
算法
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
44 0
|
15天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
62 8
|
15天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
51 7
|
1月前
|
算法 API 计算机视觉
人脸识别笔记(一):通过yuface调包(参数量54K更快更小更准的算法) 来实现人脸识别
本文介绍了YuNet系列人脸检测算法的优化和使用,包括YuNet-s和YuNet-n,以及通过yuface库和onnx在不同场景下实现人脸检测的方法。
52 1
|
1月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
67 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
18 1
|
1月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
14 0
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
32 0
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
23 0
下一篇
无影云桌面