欢迎拜访:雾里看山-CSDN博客
本篇主题:算法思想之前缀和(二)
发布时间:2025.4.11
隶属专栏:算法

目录
算法介绍
核心思想
前缀和(Prefix Sum) 是一种预处理数组的方法,通过预先计算并存储数组的累积和,将区间和查询的时间复杂度从 O(n) 优化至 O(1),适用于频繁查询子数组和的场景。
大致步骤
- 预处理出来一个前缀和数组
- 使用前缀和数组
- 处理边界情况
例题
和为 K 的子数组
题目链接
题目描述
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:输入:nums = [1,1,1], k = 2
输出:2示例 2:
输入:nums = [1,2,3], k = 3
输出:2提示:
1 <= nums.length <= 2 * 104-1000 <= nums[i] <= 1000-107 <= k <= 107
算法思路
设 i 为数组中的任意位置,用 sum[i] 表示 [0, i] 区间内所有元素的和。
想知道有多少个以 i 为结尾的和为 k 的子数组,就要找到有多少个起始位置为 x1, x2, x3... 使得 [x, i] 区间内的所有元素的和为 k 。那么 [0, x] 区间内的和是不是就是sum[i] - k 了。于是问题就变成:
- 找到在
[0, i - 1]区间内,有多少前缀和等于sum[i] - k的即可。
我们不用真的初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和等于sum[i] - k 。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。
代码实现
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> hash;
hash[0] = 1;
int n = nums.size(), sum = 0, ret = 0;
for(int i = 0; i < n; i++)
{
sum += nums[i];
if(hash[sum-k] > 0)
ret += hash[sum-k];
hash[sum]++;
}
return ret;
}
};

和可被 K 整除的子数组
题目链接
题目描述
给定一个整数数组
nums和一个整数k,返回其中元素之和可被k整除的非空 子数组 的数目。
子数组 是数组中 连续 的部分。
示例 1:输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]示例 2:
输入: nums = [5], k = 9
输出: 0提示:
1 <= nums.length <= 3 * 104-104 <= nums[i] <= 1042 <= k <= 104
算法思路
补充两个小知识
- 同余定理
如果(a - b) % n == 0,那么我们可以得到一个结论:a % n == b % n。用文字叙述就是,如果两个数相减的差能被n整除,那么这两个数对n取模的结果相同。
例如:(26 - 2) % 12 == 0,那么26 % 12 == 2 % 12 == 2。 - c++ 中负数取模的结果,以及如何修正负数取模的结果
- c++ 中关于负数的取模运算,结果是把负数当成正数,取模之后的结果加上一个负号。
例如:-1 % 3 = -(1 % 3) = -1 - 因为有负数,为了防止发生出现负数的结果,以
(a % n + n) % n的形式输出保证为正。
例如:-1 % 3 = (-1 % 3 + 3) % 3 = 2
- c++ 中关于负数的取模运算,结果是把负数当成正数,取模之后的结果加上一个负号。
设 i 为数组中的任意位置,用 sum[i] 表示 [0, i] 区间内所有元素的和。
- 想知道有多少个以
i为结尾的可被k整除的子数组,就要找到有多少个起始位置为x1, x2, x3...使得[x, i]区间内的所有元素的和可被k整除。 - 设
[0, x - 1]区间内所有元素之和等于a,[0, i]区间内所有元素的和等于b,可得(b - a) % k == 0。 - 由同余定理可得,
[0, x - 1]区间与[0, i]区间内的前缀和同余。于是问题就变成:- 找到在
[0, i - 1]区间内,有多少前缀和的余数等于sum[i] % k的即可。
- 找到在
我们不用真的初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和等于sum[i] - k 。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。
代码实现
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> hash;
hash[0%k] = 1;
int sum = 0, ret = 0;
for(auto &n : nums)
{
sum += n;
int r = (sum%k + k)%k;
if(hash[r] > 0)
ret+=hash[r];
hash[r]++;
}
return ret;
}
};

连续数组
题目链接
题目描述
给定一个二进制数组
nums, 找到含有相同数量的0和1的最长连续子数组,并返回该子数组的长度。
示例 1:
输入:nums = [0,1]
输出:2
说明:[0, 1] 是具有相同数量 0 和 1 的最长连续子数组。示例 2:
输入:nums = [0,1,0]
输出:2
说明:[0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。示例 3:
输入:nums = [0,1,1,1,1,1,0,0,0]
输出:6
解释:[1,1,1,0,0,0] 是具有相同数量 0 和 1 的最长连续子数组。
提示:
1 <= nums.length <= 105nums[i]不是0就是1
算法思路
稍微转化一下题目,就会变成我们熟悉的题:
- 本题让我们找出一段连续的区间,
0和1出现的次数相同。 - 如果将
0记为-1,1记为1,问题就变成了找出一段区间,这段区间的和等于0。
• 于是,就和 560. 和为 K 的子数组 这道题的思路一样
代码实现
class Solution {
public:
int findMaxLength(vector<int>& nums) {
unordered_map<int, int> hash;
hash[0] = -1;
int n = nums.size(), sum = 0, ret = 0;
for(int i = 0; i < n; i++)
{
sum += (nums[i] == 0 ? -1: 1);
if(hash.count(sum))
ret = max(ret, i - hash[sum]);
else
hash[sum] = i;
}
return ret;
}
};

矩阵区域和
题目链接
题目描述
给你一个
m x n的矩阵mat和一个整数k,请你返回一个矩阵answer,其中每个answer[i][j]是所有满足下述条件的元素mat[r][c]的和:
i - k <= r <= i + k,j - k <= c <= j + k且(r, c)在矩阵内。示例 1:
输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]示例 2:
输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]提示:
m == mat.lengthn == mat[i].length1 <= m, n, k <= 1001 <= mat[i][j] <= 100
算法思路
⼆维前缀和的简单应用题,关键就是我们在填写结果矩阵的时候,要找到原矩阵对应区域的左上角以及右下角的坐标(推荐画图)
- 左上角坐标:
x1 = i - k,y1 = j - k,但是由于会超过矩阵的范围,因此需要对0取一个max。因此修正后的坐标为:x1 = max(0, i - k),y1 = max(0, j - k); - 右下角坐标:
x1 = i + k,y1 = j + k,但是由于会超过矩阵的范围,因此需要对row - 1,以及col - 1取⼀个min。因此修正后的坐标为:x2 = min(row - 1, i + k),y2 = min(col - 1, j + k)。
然后将求出来的坐标代入到二维前缀和矩阵的计算公式上即可~(但是要注意下标的映射关系)
代码实现
class Solution {
public:
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
int row = mat.size(), col = mat[0].size();
vector<vector<int>> dp(row+1, vector<int>(col+1));
for(int i = 1; i <= row; i++)
for(int j = 1; j <= col; 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>> answer(row, vector<int>(col));
for(int i = 0; i < row; i++)
for(int j = 0; j < col; j++)
{
int x1 = max(0, i-k)+1, y1 = max(0, j-k)+1;
int x2 = min(row-1, i+k)+1, y2 = min(col-1, j+k)+1;
answer[i][j] = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];
}
return answer;
}
};

⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!