赞
踩
(文章的题目解释可能存在一些问题,欢迎各位小伙伴私信或评论指点(双手合十))
前缀和算法解决的是“快速得出一个连续区间的和”,以前求区间和的时间复杂度是O(N),使用前缀和可以直接降为O(1)
【模板】前缀和_牛客题霸_牛客网 (nowcoder.com)
首先解释下题目:给我们一个长度为n的数组,但是我们要注意数组的下标是从1开始的,不是从0开始的,所以我们创建数组的时候要创建n+1大小的数组。
然后是最重要的输入参数环节:一共要输入多组数据,分别如下:
- 第一组数为n和q,n表示要操作的数组长度,上面的示例中为3,q表示要对这个数组操作的次数,示例里为2,表示要进行两次操作,也就是要返回两个结果
- 第二组输入的数就是要进行操作的数组,长度为n
- 后面输入几组数据数量为第一组数据的q,示例中q为2,于是后面有两组数,假设q为3,后面就是三组数。后面的这几组数都是2个数组成,分别为l和r,表示left和right
- 然后就是操作部分,以示例为离,第三组数为1和2,所以就计算数组包含中1下标和2下标的区间内所有值的和,最后输出1+2=3;第四组数为2和3,计算数组中2和3下标区间的和,输出2+4=6。下面来分析下这道题:
dp[R] - dp[L - 1]
问题:为什么下标要从1开始计数
解答: 在力扣上给我们的数组下标基本都是从1开始计数。就上面的公式:dp[R] - dp[L - 1]为例,假设计算[0, 2]区间的和,那么公式就是dp[2] - dp[-1]越界了,需要处理边界问题;但是下标从1开始的话就不用处理。
并且由于dp的构成,导致dp[0]这个位置没有数,我们直接设置为0即可,因为0加任何数都等于这个数
- #include <iostream>
- #include<vector>
- using namespace std;
-
- int main()
- {
- //1,读入数据
- int n, q;
- cin >> n >> q;
- vector<int> arr(n + 1); //要n+1,因为下标从1开始
- for(int i = 1; i <= n; i++) cin >> arr[i]; //输入目标数组
-
- //2,构建前缀和数组
- vector<long long> dp(n + 1); //用long long防止溢出
- for(int i = 1; i <= n; i++) dp[i] = dp[i-1] + arr[i];
-
- //3,使用前缀和
- int l = 0, r = 0;
- while(q--)
- {
- cin >> l >> r;
- cout << dp[r] - dp[l - 1] << endl;;
- }
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com)
题目和第一题一样,只不过是把要操作的数组从一维变成了二维,后面的操作部分的一组数是4个数,前面两个数和后面两个数各自表示矩阵中的某个数的位置,求由这样两个数对角形成的正方形里所有数的和,其余操作一致,下面来分析下这道题:
- #include <iostream>
- #include<vector>
- using namespace std;
-
- int main()
- {
- //1,读入数据
- int n = 0, m = 0, q = 0;
- cin >> n >> m >> q;
- vector<vector<int>> arr(n + 1, vector<int>(m + 1));
- for(int i = 1; i <= n; i++)
- for(int j = 1; j <= m; j++)
- cin >> arr[i][j];
-
- //2,构建dp前缀和矩阵
- vector<vector<long long>> dp(n + 1, vector<long long>(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]; //公式
-
- //3,使用dp前缀和矩阵
- while(q--)
- {
- int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
- cin >> x1 >> y1 >> x2 >> y2;
- cout << (dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]) << endl;
- }
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
题目就不解释了哈,因为感觉我解释得可能还没原题好。下面来分析下这道题:
细节问题:
- 初始化的时候,f[0]需要先赋值为0,不然会越界访问,同理g[n-1],在公式里就变成了g[n-1] = g[n] + nums[n],也越界访问了,所以g[n-1]也要赋值为0
- 然后是填表顺序,在填写f数组的时候是从左往右;但是填g数组的时候是从右往左
- class Solution {
- public:
- int pivotIndex(vector<int>& nums) {
- int n = nums.size();
- vector<int> f(n), g(n); //前缀和,后缀和数组
-
- //1,构建前缀和数组和后缀和数组
- for(int i = 1; i < n ; i++)
- f[i] = f[i - 1] + nums[i - 1];
- for(int i = n - 2; i >= 0; i--)
- g[i] = g[i + 1] + nums[i + 1];
-
- //2,使用前缀和数组
- for(int i = 0; i < n; i++) //枚举中心下标
- {
- if(f[i] == g[i]) return i;
- }
- return -1;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
238. 除自身以外数组的乘积 - 力扣(LeetCode)
解释下题目:给我们一个数组nums,让我们再构建一个数组answer返回,其中answer数组的answer[i]表示的含义是在nums中除nums[i]以外的其余各元素的和,相乘时题目保证不会溢出。下面来解释下这道题:
处理细节问题:
- 和上面一样又不一样,f(0) = 1,g(n-1) = 1
- class Solution
- {
- public:
- vector<int> productExceptSelf(vector<int>& nums)
- {
- int n = nums.size();
- vector<int> f(n), g(n);
- f[0] = g[n-1] = 1;
-
- //1,构建前缀积和后缀积数组
- for(int i = 1; i < n; i++)
- f[i] = f[i-1] * nums[i-1];
- for(int i = n - 2; i >= 0; i--)
- g[i] = g[i+1] * nums[i+1];
-
- //2,使用
- vector<int> ret(n);
- for(int i = 0; i < n; i++)
- ret[i] = f[i] * g[i];
-
- return ret;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
题目描述很简单,并且上面的示例很好懂,下面来解释下这道题:
细节问题:
- 首先是前缀和假如哈希表的时机:先把所有前缀和全算出来,然后全扔哈希表里去。这个方法不行的,我们要算的是i位置之前的前缀和等于K的数量,全扔进去的话可能会统计到i位置之后的;所以在计算i位置之前,哈希表里只保存[0, i - 1]位置的前缀和
- 我们其实不用创建一个前缀和数组:在计算dp[i]时,原先的公式是:dp[i] = dp[i-1] + nums[i],但是我们可以不创建前缀和数组,所以我们可以用以后个变量sum来标识dp[i-1],然后公式就可以写成dp[i] = sum + nums[i]; sum = dp[i],依次循环,就可以不创建前缀和数组从而求出前缀和
- 如果整个前缀和等于K呢?假设一个区间[0, i]为K,所以按照前面的思路应该就算去[0, -1]这个区间去找和为0的区间,但是不存在-1区间,但是这也是一种情况,不能忽略。所以写代码时创建哈希表时,要先hash[0] = 1,避免“整个数组前缀和为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 x : nums)
- {
- sum += x; //计算当前位置的前缀和
- if(hash.count(sum - k)) ret += hash[sum - k]; //如果该前缀和符合要求,就统计该前缀和出现的次数
- hash[sum]++;
- }
- return ret;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
974. 和可被 K 整除的子数组 - 力扣(LeetCode)
这道题是某一年蓝桥杯竞赛原题,先解释下题目:这道题描述和上面“和为K的子数组”很像,给一个整数数组nums和一个整数K,要求我们返回连续的子数组,该子数组的要求是,子数组元素的和可被K整除。下面来分析下这道题:
补充下知识:
- 同余定理:就算一个数学公式:(a - b) / p = k 余零,那么我们可以认为:a % p == b % p。举个例子:(26 - 12) / 7 = 2 余零,那么可以得出 (26 % 7) == (12 % 7) == 5。取余的本质其实就算让一个数不断的减另一个数,以7 % 2为例:7 % 2 = (7 - 2 -2 -2) = 1,所以我们也可以看作(1 + 2 * 3) % 2 = 1,所以我们就又可以得出一个公式:(a + p * k) % p = a%p。
- C++, java 负数% 正数的结果以及修正:
下面来分析下这道题,解题思路和上面一道题很像
- class Solution
- {
- public:
- int subarraysDivByK(vector<int>& nums, int k)
- {
- unordered_map<int, int> hash; //统计前缀和出现的次数,第一个int为前缀和余数,第二个int表示次数
- hash[0] = 1; //0的余数只能是0,所以要单独处理一下
-
- int sum = 0, ret = 0;
- for(auto x : nums)
- {
- sum += x; //计算当前位置的前缀和
- int r = (sum % k + k) % k; //计算前缀和余数,修正后的余数
- if(hash.count(r)) ret += hash[r]; //如果该前缀和余数符合要求,就统计该前缀和余数出现的次数
- hash[r]++;
- }
- return ret;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
解释下题目:给一个二进制数组,这个数组只有0和1两种元素,找到含有相同数量的0和1的最长子数组,返回长度。题目很简单,下面来分析下这道题:
处理细节:
- 哈希表里存什么?在这道题里,我们不关心前缀和有多少个,我们只关心你的长度,所以<int, int>,第一个int还是前缀和不变,第二个int不存个数了,存的应该是下标
- 什么时候存入哈希表?在sum和下标i使用完成之后再扔哈希表里去
- 如果出现重复的<sum, i>,如何存?当 i = 9时,sum符合要求,但是假设当j = 4时,sum也符合要求,这时,我们应该选择更靠前的下标,因为下标越靠前,算出来的子数组越长哦,所以要存<sum, j>
- 默认的前缀和的和为0的情况,如何存?当整个数组的sum为0时,我们是要在nums[-1]这个位置寻找前缀和为0的数组的,因此在上一道题里面我们是hash[0] = 1,表示默认有一个前缀和为0的,表示前缀和为0的数组有一个,上面那一道题村的是个数,这道题我们要存的是下标,所以这道题我们要hash[0] = -1
- 长度怎么算?
- class Solution
- {
- public:
- int findMaxLength(vector<int>& nums)
- {
- unordered_map<int, int> hash; //细节1
- hash[0] = -1; //细节4
-
- int sum = 0, ret = 0;
- for(int i = 0; i < nums.size(); i++)
- {
- if(nums[i] == 0) sum += -1; //将0变成-1
- else sum += 1;
- if(hash.count(sum)) ret = max(ret, i - hash[sum]); //细节3,推导长的那一个数组的长度
- else hash[sum] = i; //如果存在相同的sum,就更新长的那一个数组,如果是新的sum,就扔哈希表里去
- }
- return ret;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
前面的都是一维前缀和题目,是时候做一下二维前缀和了。题目如下图: 下面来解释下这道题:
- class Solution
- {
- public:
- vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k)
- {
- int m = mat.size(), n = mat[0].size(); //计算原矩阵的长和宽大小
-
- //1,构建前缀和dp矩阵
- vector<vector<int>> dp(m + 1, vector<int>(n + 1));
- for(int i = 1; i <= m; i++)
- for(int j = 1; j <= n; j++)
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + mat[i - 1][j - 1];
-
- //2,使用前缀和矩阵
- vector<vector<int>> ret(m, vector<int>(n));
- for(int i = 0; i < m; i++)
- for(int j = 0; j < n; j++)
- {
- int x1 = max(0, i - k) + 1, y1 = max(0, j - k) + 1;
- int x2 = min(m - 1, i + k) + 1, y2 = min(n - 1, j + k) + 1;
- ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
- }
- return ret;
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。