欢迎拜访:雾里看山-CSDN博客
本篇主题:算法思想之前缀和(二)
发布时间:2025.4.11
隶属专栏:算法
目录
- 滑动窗口算法介绍
- 核心思想
- 大致步骤
- 例题
- 和为 K 的子数组
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 和可被 K 整除的子数组
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 连续数组
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 矩阵区域和
- 题目链接
- 题目描述
- 算法思路
- 代码实现
滑动窗口算法介绍
核心思想
前缀和(Prefix Sum
) 是一种预处理数组的方法,通过预先计算并存储数组的累积和,将区间和查询的时间复杂度从 O(n)
优化至 O(1)
,适用于频繁查询子数组和的场景。
大致步骤
- 预处理出来一个前缀和数组
- 使用前缀和数组
- 处理边界情况
例题
和为 K 的子数组
题目链接
560. 和为 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 整除的子数组
题目链接
974. 和可被 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] <= 104
2 <= 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;}
};
连续数组
题目链接
525. 连续数组
题目描述
给定一个二进制数组
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 <= 105
nums[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;}
};
矩阵区域和
题目链接
1314. 矩阵区域和
题目描述
给你一个
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.length
n == mat[i].length
1 <= m, n, k <= 100
1 <= 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;}
};
⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!