前缀和算法 用于高效地计算 数组或序列 中某个区间内元素的和。
前缀和数组是一个辅助数组,其每个元素存储原始数组从开头到当前位置的元素和。通过提前计算前缀和数组,可以在O(1)的时间复杂度内快速计算出任意区间内的元素和。
这道题目帮助我们 理解前缀和模板 和 使用前缀和数组 :
思路
题意分析:题目要求我们返回 数组中l~r范围内所有元素的和
我们引出前缀和数组dp的使用:
解法:前缀和数组
dp[i] = dp[i-1] + arr[i]
进行dp数组的初始化(预处理)dp[r]-dp[l-1]
代码
int main() {
int n = 0, q = 0;
cin >> n >> q;
vector<int> arr(n+1); // 下标从1开始,数组大小为n+1
// 写入数组
for(int i = 1; i <= n; ++i) cin >> arr[i];
// 预处理前缀和数组
vector<long long> dp(n+1); // long long 防止溢出
for(int i = 1; i <= n; ++i) dp[i] = dp[i-1] + arr[i];
// 使用前缀和数组
while(q--)
{
int l, r;
cin >> l >> r;
cout << dp[r] - dp[l-1] << endl;
}
return 0;
}
思路
p[i] = p[i-1] + nums[i-1];
s[i] = s[i+1] + nums[i+1];
代码
int pivotIndex(vector<int>& nums) {
int n = nums.size();
// 预处理前缀和数组
vector<int> p(n); // P[i]:[0, i-1] 之间所有元素之和;
for(int i = 1; i < n; ++i) // p[0] == 0 s[n-1] == 0]
p[i] = p[i-1] + nums[i-1];
// 预处理后缀和数组
vector<int> s(n); // s[i]:[i+1, n-1] 之间所有元素之和
for(int i = n - 2; i >= 0; i--)
s[i] = s[i+1] + nums[i+1];
// 通过前缀和/后缀和数组找到中心下标
int i;
for(i = 0; i <= n - 1; ++i)
{
if(p[i] == s[i])
return i;
}
return -1;
}
思路
代码
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> p(n), s(n);
p[0] = s[n-1] = 1; // 边界条件
// 构建前缀积数组
for(int i = 1; i <= n - 1; ++i)
p[i] = p[i-1] * nums[i-1];
// 构建后缀积数组
for(int i = n-2; i >= 0; --i)
s[i] = s[i+1] * nums[i+1];
vector<int> answer(n);
for(int i = 0; i < n; ++i)
answer[i] = p[i] * s[i];
return answer;
}
思路
代码
int subarraySum(vector<int>& nums, int k) {
int sum = 0, count = 0; // sum存储前缀和
unordered_map<int, int> hash;
hash[0] = 1; // 特殊情况:当子数组的第一个元素就满足条件的情况时
for(int x : nums)
{
sum += x;
// 以x为结尾的子数组,值为sum-k,则存在满足和为k的子数组
if(hash.count(sum-k)) count += hash[sum-k];
hash[sum]++;
}
return count;
}
思路
代码
int subarraysDivByK(vector<int>& nums, int k) {
// C++,java 中负数%正数=负数
// 为了使余数满足题目条件,余数计算为(sum % k + k) % k
int sum = 0, count = 0;
unordered_map<int, int> hash;
hash[0 % k] = 1; // hash[0] = 1;
for(int x : nums)
{
sum += x;
int r = (sum % k + k) % k;
if(hash.count(r)) count += hash[r];
hash[r]++;
}
return count;
}
思路
代码
int findMaxLength(vector<int>& nums) {
int sum = 0, ret = 0;
// 将数组中的0改为-1,题目可以演化为:求和为0的子数组
//for(int &x : nums) x = 0 ? -1 : x;
unordered_map<int, int> hash; // 哈希表存放前缀和以及下标
hash[0] = -1;
for(int i = 0; i < nums.size(); ++i){
sum += nums[i] == 0 ? -1 : 1; // 更新前缀和
if(hash.count(sum)) // 前缀和sum存在 则更新ret(hash[sum] 为前缀和尾部下标, i-hash[sum] 为 连续数组长度)
ret = max(ret, i - hash[sum]);
else
hash[sum] = i;
}
return ret;
}
思路
题意分析:题目要求返回answer矩阵,矩阵每一位元素可以理解为是以mat的每一位为中心,向上下左右分别扩展k个单位的元素总和。
解法:二维前缀和
需要注意的是,最好不要死记模板公式,理解了过程,做题的时候可以模拟,自然会想出来过程
代码
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] + mat[i-1][j-1] - dp[i-1][j-1];
// 使用前缀和矩阵 构建answer
vector<vector<int>> answer(m, vector<int>(n));
for(int i = 0; i < m; ++i)
{
for(int j = 0; j < n; ++j)
{
// answer[0][0] 对应 dp[1][1],把坐标+1
int x1 = max(i-k, 0) + 1, y1= max(j-k, 0) + 1;
int x2 = min(i+k, m-1) + 1, y2 = min(j+k, n-1) + 1;
answer[i][j] = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1];
}
}
return answer;
}