【模版】前缀和
【模板】前缀和_牛客题霸_牛客网
思路
要想快速找出某一连续区间的和,我们就要使用前缀和算法。
其实本质是再创建一个dp数组,每进一次循环加上原数组的值(dp代表arr的前n项和):
vector<int> arr(n + 1);
for(int i = 1; i <= n; i++) cin >> arr[i];vector<long long> dp(n + 1);
for(int i = 1; i <= n; i++) dp[i] = dp[i - 1] + arr[i];
为防止i取0时i-1等于-1,我们从i = 1开始,当i等于0时数组值默认为0相加不会改变其值。
而我们需要某一特定区间的和,将dp数组的dp[r] - dp[l - 1]即可,例如我要求2到4区间用dp[4]-dp[1]即可:
代码
#include <iostream>
#include <vector>
using namespace std;int main()
{int n = 0, q = 0;cin >> n >> q;vector<int> arr(n + 1);for(int i = 1; i <= n; i++) cin >> arr[i];vector<long long> dp(n + 1);for(int i = 1; i <= n; i++) dp[i] = dp[i - 1] + arr[i];int l = 0, r = 0;while(q--){cin >> l >> r;cout << dp[r] - dp[l - 1] << endl;}return 0;
}
【模板】二维前缀和
【模板】二维前缀和_牛客题霸_牛客网
思路
本题暴力解法即为题目要求什么就求什么,时间复杂度为O(n*m*q)。因为此题为求某一区间的和,所以我们想到使用前缀和解题,本题为二维数组,所以是二维前缀和。
解法分为三步:第一步,读入数据
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];
第二步,预处理
创建一个dp二维数组,将arr的前n项和加起来,将dp分为四份即为总面积,其中BC宽度均为1,。为方便求面积,我们用(A + B) + (A + C) + D - A来表示总面积。A+B是dp[i-1][j] B+C是dp[i][j-1],D即为arr[i][j].A即为dp[i-1][j-1].
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];
第三步,使用前缀和矩阵
如预处理,求某一块的面积,例如(x1, y1)到(x2, y2),即为A+B+C+D-(A+B)-(A+C)+A : dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1].
代码
#include <iostream>
#include <vector>
using namespace std;int main()
{//读入数据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];//预处理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];//使用前缀和矩阵int x1 = 0, y1 = 0, x2 = 0, y2 = 0;while (q--){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;
}
寻找数组的中心下标
724. 寻找数组的中心下标 - 力扣(LeetCode)
思路
我们要让左边之和等于右边之和,我们创建两个dp数组分别存放左右两边数字之和,并分别记作f, g。f[i] = f[i - 1] + nums[i - 1], 并且由于存在i - 1,所以 i需要从1开始,否则就会出现f[-1]的情况;
同理,右边之和g[j] = g[j + 1] + nums[j +1], 并且由于存在i + 1,所以j需要从n - 2开始,否则就会出现f[n]的情况。
代码
int pivotIndex(vector<int>& nums)
{int n = nums.size();vector<int> f(n), g(n);//预处理前缀和数组和后缀和数组for (int i = 1; i < n; i++)f[i] = f[i - 1] + nums[i - 1];for (int j = n - 2; j >= 0; j--)g[j] = g[j + 1] + nums[j + 1];//使用for (int i = 0; i < n; i++)if (f[i] == g[i]) return i;return -1;
}
除自身以外数组的乘积
238. 除自身以外数组的乘积 - 力扣(LeetCode)
思路
与上一题类似,要求除次下标外其他数的乘积,我们将其转化为i左边乘积乘上右边乘积即可:
左:f[i] = f[i - 1] * nums[i - 1]
右:g[j] = g[j + 1] * nums[j +1]
需要注意的是f[0]和g[n - 1]的初始值是0,这样会导致所求得的值都为0, 所以与上一题不同的是,我们需要将f[0]和g[n - 1]的初始值设为1,这样就不会影响所求得的值了。
代码
vector<int> productExceptSelf(vector<int>& nums)
{int n = nums.size();vector<int> f(n), g(n), answer(n);f[0] = 1, g[n - 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];}for (int i = 0; i < n; i++){answer[i] = f[i] * g[i];}return answer;
}
和为k的子数组
560. 和为 K 的子数组 - 力扣(LeetCode)
思路
取中间点m,m到i即为所要的区间,可以转化为计算0到m区间之和,和为sum - k。通过计算前缀和等于sum-k即可判断要求区间等于K,我们将前缀和放入不重复的哈希表中,最后通过hash.count()返回子数组的个数。
代码
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;}
和可被K整除的子数组
974. 和可被 K 整除的子数组 - 力扣(LeetCode)
思路
本题需要补充两个相关知识:
余数定理
若两个数之差可被k整除(模k等于0),则两个数模k的值相等
c++和java负数模k
c++和java负数模k还是负数,所以我们需要用下面的式子将其修正(变为正数):a % k = (a % k + k) % k.
了解这两个知识,本题就与上题极其相似,只不过是sum-k变成了sum模k了。
代码
int subarraysDivByK(vector<int>& nums, int k) {unordered_map<int, int> hash;hash[0] = 1;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;}
矩阵区域和
1314. 矩阵区域和 - 力扣(LeetCode)
思路
本题需要二维数组的知识,可使用前面的二维数组模板。
本题题意是选取某一位置,计算其周围一圈数字的和(包括它本身),像这样:
为防止dp表越界问题,我们仍然从1开始计数,所以创建dp数组时,mat[i][j]要变成mat[i-1][j-1].
如图为原数组mat与dp数组的关系,dp数组相当于左边加一列,上面加一行,所以使用mat数组时需要减一。
为了防止选取数字周围发生越界,我们将选取矩阵的左上角与右下角坐标(x1, y1),(x2, y2)的关系了解清楚:
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;
如果是负数就与0比大小,如果太大了就与最大值比大小。
同样地,因为dp数组从1开始计数,所以将mat表中数据需要加一
代码
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k)
{int m = mat.size(), n = mat[0].size();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];vector<vector<int>> ret(m, vector<int>(n));int x1 = 0, x2 = 0;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;
}