【优选算法 & 前缀和】前缀和算法模板详解:一维前缀 & 与二维前缀和

发布于:2024-12-08 ⋅ 阅读:(174) ⋅ 点赞:(0)

      

 


一维前缀和


    题目解析   


 


    算法原理    



     解法一:暴力解法    


简单模拟,读完题意有 q 次询问,给哪两个数,就求哪段区间的和并且返回,这样的做法,时间复杂度为O(N*q),这个时间复杂度会超时;


     解法二:前缀和     


 前缀和算法的应用场景,就是快速求出数组中某一个连续区间的和

暴力解法需要从头到尾遍历一次数组,时间复杂度为O(N),而前缀和可以在连续区间以一个O(1)的时间复杂度求和;所以有q次求和,总的时间复杂度为O(q);


    (1) 预处理出一个前缀和数组    


预处理出来的前缀和数组,我们命名为 dp ;

并且与原始数组 arr 相同大小,比如 arr 的下标是从1到9,那么 dp 的下标也是从 1 到 9:


dp 的每一个元素 dp[i],表示 [ 1 , i ] 区间内所有元素的和:

小技巧:dp[ i +1] = dp[ i ] + arr [ i ]


    (2) 使用前缀和数组    


有了前缀和数组,如果要求 [ l , r ] 区间的和,只需要求 dp[ r ] - dp [ l -1 ] 即可;

 而使用前缀和数组来对一段连续区间进行求和,时间复杂度为 O(1),如果有 q 次求和,总的时间复杂度为 O(N) + O(q)


 我们可以把同一类问题,抽象成一个状态表示;


     (3) 处理细节问题     


为什么下标要从 1 开始计数呢?如果从 0 开始计数,当有一次要查询 [ 0 , 2 ] 区间的和:

带入这条公式,就变成了 dp[2] - dp[ -1],数组无法访问 -1 位置的元素,所以出现这种情况需要特殊处理;

而数组从 1 下标位置开始计数,查询 [ 1 , 3] 区间的和,就变成了 dp[3] - dp[ 0 ] ,我们只需要把 dp [0] 置为 0即可,避免处理边界情况; 

在动态规划中 ,把 dp[0]置为0,就是添加辅助节点的步骤,添加辅助节点来帮助初始化,能够避免处理一些边界情况;


    编写代码    




二维前缀和


  

    题目解析   



    算法原理    


     解法一:暴力解法    


 通过给出的坐标,直接求出子矩阵所有元素之和即可;时间复杂度,每次询问,最差情况是都得求出子矩阵中每个元素之和,就得遍历整个矩阵一遍,时间复杂度为O(n*m*q);


     解法二:前缀和     


    (1) 预处理出一个前缀和矩阵    


重新创建一个同等规模的矩阵 dp,dp[i][j] 表示从 [1,1] 到 [ i , j ] 里所有元素之和 ;

我们需要快速找出规律,来求出 dp[i][j] 的值是多少,否则每求一个元素,就要遍历一次用来的矩阵,那么创建 dp 表的时间复杂度也会很大;


我们可以根据所求的  dp[i][j],将原来的 A 矩阵分成四个部分,最终要求的 dp[i][j] 等于 A+B+C+D,可以类比面积的求和过程;dp[ i-1][ j-1 ]=A,但是 B C 区域不好求:

我们可以转换思路,求AB,AC再减去重复计算的部分,即可求出 B,C区域;并且,我们总结出了求 dp[i][j] 的公式: 

通过公式,就可以用公式以O(1) 的时间复杂度,来预处理前缀和矩阵;


    (2) 使用前缀和矩阵    


创建好前缀和矩阵后,该怎么使用呢?我们通过分割 :


转换成公式如下:

当得到递推公式,每次拿到 [x1 , y1] ~ [ x2 , y2 ],直接套用这条公式,就可以以 O(1) 的时间复杂度来求和两个坐标围成的子矩阵;


对比暴力解法,使用前缀和矩阵的时间复杂度为 ,在预处理前缀和数组的时间复杂度为 O(m*n),再查询 q 次求和结果,时间复杂度 O(q);得到的总时间复杂度 O( m*n + q )


    编写代码