一维前缀和
题目解析
算法原理
解法一:暴力解法
简单模拟,读完题意有 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 );
编写代码