1.【模板】前缀和
思路:
板子题,思路前缀和数组记录 。
虽是板子,但以后切记不要死记模版。
主要要开long long,防溢出。
#include <iostream>
using namespace std;
#include<vector>
int main()
{
int n = 0, q = 0;
cin >> n >> q;
vector<int> arr(n);
for(int i = 0; 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 - 1];
//q次查询
while(q--)
{
int l = 0, r = 0;
cin >> l >> r;
cout << dp[r] - dp[l - 1] << endl;
}
return 0;
}
2.【模板】二维前缀和
思路:
二维的模板题。
此图借助理解
#include <iostream>
using namespace std;
#include<vector>
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];
//填充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][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1] + arr[i][j];
while(q--)
{
int x1 = 0, y1 = 0, x2 = 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;
}
3.寻找数组的中心下标
思路:
求解前缀和与后缀和
比较i位置的前缀和与后缀和是否相等
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int n = nums.size();
//f[i]表示:[0, i - 1]内的所有数的和
//g[i]表示:[i + 1, n - 1]内的所有数的和
vector<int> f(n + 1), 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];
for(int i = 0; i < n; i++)
{
if(f[i] == g[i]) return i;
}
return -1;
}
};
4.除自身以外数组的乘积
思路:
跟3类似,只是f[i]与g[i]的状态发生了变化,变为了乘法。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
//填充dp表
vector<int> f(n), g(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++)
{
ans[i] = f[i] * g[i];
}
return ans;
}
};
5.和为k的子数组
思路:
前缀和与哈希表结合,让我们求子数组和为k,即让我们求前缀和的sum - k出现的次数。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int sum = 0, ret = 0;
unordered_map<int, int> hash; // 记录子数组和为k的个数
// 细节问题 - 子数组为0时的数量默认为1
hash[0] = 1;
for(auto x : nums)
{
sum += x;// 计算当前位置的前缀和
if(hash.count(sum - k)) ret += hash[sum - k];//更新结果
hash[sum]++;// 统计子数组和的次数
}
return ret;
}
};
6.和可被k整除的子数组
思路:
与4类似,只是hash里面存的内容有所不同
同余定理:(sum - x) % p = 0 -> sum % p == x % p.
c++/java中的负数%正数的修正(a%b + b) % b
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> hash;//记录前缀余数出现的次数
//处理细节问题
hash[0 % k] = 1;//0 - 代表余数为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;
}
};
7.连续数组
思路:
与和为k的子数组类似
细节问题比较特殊
哈希表存放的是什么?
hash【0】应该存什么?
class Solution {
public:
int findMaxLength(vector<int>& nums) {
//把0都转化为-1,就可以用我们的前缀和求解了-> 实质就是求子数组为k时对应的子数组长度
unordered_map<int, int> hash;// 存放的是sum的对应下标
//处理细节问题
hash[0] = -1;// hash表内存放的是下标,所以前缀和为0只能是-1
int sum = 0, ret = 0;
for(int i = 0; i < nums.size(); i++)
{
sum += nums[i] == 0 ? -1 : 1; // 计算当前位置的前缀和
if(hash.count(sum)) ret = max(ret, i - hash[sum]); //更新结果
else hash[sum] = i; //前缀和进入哈希表
}
return ret;
}
};
8.矩阵区域和
思路:
二维前缀和
细节:处理映射关系上如何处理?
class Solution {
public:
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
int m = mat.size(), n = mat[0].size();
//创建dp表
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
//填写dp表
//注意dp表与mat矩阵的映射关系mat[i - 1][j - 1]还是mat[i][j]
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));
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
//使dp数组下标映射和原数组对齐
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[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1];
}
return ret;
}
};