开个系列记录学姐带我学算法的过程啦
第一节课前缀和主要是通过两个题来理解。
acwing-795t
我在此之前完全没有接触过算法,只学了c语言和c++基础语法(说句题外话,其实c++或者说很多我们看着陌生的东西,内心好像感觉很难,但其实见得多了就习惯了,不要因为此时的你看不懂就觉得后面没法进行下去)。
所以在学姐提前发出这些题的时候,我采取的方式都是暴力做法😂
#include<stdio.h>
int Sum(int l,int r,int arr[])
{
int sum=0;
for(int i=l;i<=r;i++)
{
sum+=arr[i];
}
return sum;
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
int arr[n];
for(int i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
int l,r;
for(int i=0;i<m;i++)//这里有点混用了c++
{
scanf("%d %d",&l,&r);
printf("%d\n",Sum(l-1,r-1,arr));
}
}
由于在比赛过程中或者题目要求会对时间和空间有限制(我对这方面概念还不是很深 等之后有机会再说吧),我理解为代码运行的效率。下面我解释一下这段代码的时间复杂度,
补充一下时间复杂度:
理解为执行操作的次数。
当代码中没有递归或者循环时,例如
#include<stdio.h>
int main()
{
int a,b,sum;
scanf("%d%d",&a,&b);
sum=a+b;
printf("%d",sum);
return 0;
}
此时时间复杂度为O(1),这意味着无论输入的整数有多大,这段代码都能在相同的时间内运行完毕。这是最低的时间复杂度,也是最理想的情况。
当存在并列循环的时候(没有嵌套),时间复杂度为循环次数相加。
当存在嵌套循环时,时间复杂度为循环次数相乘。
(时间复杂度先浅浅说到这里)
在这段代码主函数中并列for循环,且循环执行n+m次,此时是时间复杂度为O(n+m),但最后输出的时候,调用Sum函数中存在for循环,可理解为循环的嵌套,在Sum函数中,for循环执行次数为r-l+1,由于r和l是由输入决定的,我们不能确定它们的具体值,所以只能用最坏情况来估计时间复杂度。最坏情况是当r和l相差最大时,也就是r=n-1,l=0时,执行次数为n次,所以时间复杂度为 O(n*(m+1))。
为了降低时间复杂度,出现了更为高效的计算方法,前缀和算法,作用是快速地求出一个数组或矩阵中某个区间或子区域的和。
算法思想是:开辟一个新的数组,存放从第一个元素开始到当前元素的累加和,再通过前缀和之间的差来求出任意区间(一维)或子区域(二维)的和。
用图像的形式很清晰的看到我们所需要的两个数组对叭,如何用代码实现呢?p[i]就不用说啦,一个for循环就可以啦。主要是q[i],要明白它所表示的含义是,从第一个元素开始到当前元素的累加。用q[i]=q[i-1]+p[i] 的方式表示,想一下可以理解撒
当我们想要得到从第一个元素到第三个元素的和时,应该是q[3]-q[0]=6-0 从第二个元素到第四个元素的和时,应该是q[4]-q[1]=16-3 由此可以看出所求结果应该是q[r]-q[l-1]
完整代码如下,具体的细节都有注释
//前缀和算法
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m;
int p[N],q[N];
//把变量定义到主函数外面 会自动初始化0
int main()
{
//紧盯输入格式来写
scanf("%d %d",&n,&m);//n表示数组元素个数,m表示询问次数(测试个数)
for(int i=1;i<=n;i++)//这里使数组下标从1开始存储数据,避免了下面q[i-1]出错。而且由于将数组定义为全局变量,自动初始化为0,q[0]=0方便了很多
{
scanf("%d ",&p[i]);
q[i]=q[i-1]+p[i];
}
while(m--)//m--作为循环判断条件 后置-- 先使用后--
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",q[r]-q[l-1]);
}
return 0;
}
ok啦,一维数组前缀和这道题就先到这里啦。
acwing-796t
有了上面的知识,对于二维数组我们来比葫芦画瓢一下。
首先需要开辟一个数组,来存放当前元素与第一个元素所围成区域的区域内的数字的总和(二维的)
是长这个样子的(q[i]也写出来是为了更清晰一点)。那么p[i]不用多说两个for循环嵌套输入即可,q[i]怎么得到呢?想一下一维的时候我们是通过它前一项加上这一点的值得到的,那二维应该也可以采用这种方式,如下图
假设(i,j) 是(2,3),那么它和坐标为(1,1)的第一个元素所围成的区域为图中红色框框,想要求得红框,就等于黄框+绿框-黄绿重叠部分+(i,j)即可。等式由此得来。
那么想要求任意(x1,y1)(x2,y2)所围成的区域的和
如图,找准点即可,且时刻记住q[i]这个数组存放的就是该点的前缀和(因为我发现我看不懂的时候就是因为忘记了q数组本身的含义emm)
完整代码如下
#include<iostream>
using namespace std;
const int N=1010;
int p[N][N],q[N][N];
int n,m,k;
int main()
{
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d ",&p[i][j]);
q[i][j]=q[i-1][j]+q[i][j-1]-q[i-1][j-1]+p[i][j];
}
}
while(k--)
{
int x1,y1,x2,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
printf("%d\n",q[x2][y2]-q[x1-1][y2]-q[x2][y1-1]+q[x1-1][y1-1]);
}
return 0;
}
好啦。这节课的笔记先到这里啦。
本人是小白,算法学习才刚刚开始,也是第一次写博客(hhh其实看鹏哥c语言的时候就知道要写博客但是一直没开始😓),受到学姐的鼓励终于开始啦,相信对学习有帮助。欢迎交流和建议😄