前言
本篇带来前缀和的最终部分,与之前相同,将结合难度进一步提升的题目进行详细分析讲解,以深化对该算法的理解运用。
一. 和可被k整除的子数组
1.1 题目链接:https://leetcode.cn/problems/subarray-sums-divisible-by-k/description/
1.2 题目分析:
该题与上篇和为K的子数组要求类似,只是此处要求和可被K整除。
1.3 思路讲解
在此之前,我们先补充一下本题需要使用的数学知识:同余定理。
如果 (a - b) % n == 0 ,那么我们可以得到⼀个结论: a % n == b % n 。***⽤⽂字叙
述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。例如: (26 - 2) % 12 == 0 ,那么 26 % 12 == 2 % 12 == 2 。
余数修正 |
c++ 中负数取模的结果,以及如何修正「负数取模」的结果:
a. c++ 中关于负数的取模运算,结果是「把负数当成正数,取模之后的结果加上⼀个负号」。 例如: -1 % 3 = -(1 % 3) =-1
b. 因为有负数,为了防⽌发⽣「出现负数」的结果,以 (a % n + n) % n 的形式输出保证为 正。 例如: -1 % 3 = (-1 % 3 + 3) % 3 = 2
暴力解法(会超时):
与之前相同,逐个遍历枚举。
前缀和:
- . 首先,我们用sum[i]表示下标为i的元素的前缀和。
如图,和可被K整除的子数组,即代表[x,i]内的元素和sum[i]-sum[x]可被k整除
- 由同余定理可得,(sum[i]-sum[x])%k==0,可得出sum[i]%k与sum[x]%k相等。
- 因此问题就转化为,求有多少个前缀和可被k整除的数组,即找到在 [0, i - 1] 区间内,有多少前缀和的余数等于 sum[i] % k 的即可。
- 同时由于负数取模的性质,在取模时需要对余数进行修正。
1.4 代码实现:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int,int> hash;//记录前缀和及出现次数
int sum=0,ret=0;
hash[0%k]=1;
for(int i=0;i<nums.size();i++)
{
sum+=nums[i];
int r=(sum%k+k)%k;//修正余数
if(hash.count(r))
{
ret+=hash[r];
}//说明此时该子数组可被k整除
hash[r]++;
}
return ret;
}
};
二. 连续数组
2.1 题目链接:https://leetcode.cn/problems/contiguous-array/description/
2.2 题目分析:
题目要求找出最长的连续子数组,且该子数组内0和1的个数相等
2.3 思路讲解:
暴力解法(会超时):
枚举所有的子数组,判断是否满足条件。
前缀和:
题目的关键之处在于,所求的最长子数组内0和1的个数相等,暴力算法内的时间冗余也主要在判断是否相等上。
不妨将题目内的0变为-1,那么此时符合条件的子数组所有元素和则必然为0,大大简化了判断条件。
具体步骤如下:
- 采用哈希表记录前缀和及其出现的位置,sum来记录当前的和,ret记录返回的数组长度
- 当元素为0时,sum-1,当元素为1时,sum+1
- 当满足条件时,更新ret,否则则更新哈希表
2.4 代码实现:
class Solution {
public:
int findMaxLength(vector<int>& nums) {
unordered_map<int,int> hash;//记录前缀和及出现的位置
int sum=0,ret=0;
hash[0]=-1;//记录起始位置,防止最大子数组为数组本身的情况
for(int i=0;i<nums.size();i++)
{
sum+=nums[i]==0?-1:1;
if(hash.count(sum))
{
ret=max(ret,i-hash[sum]);
}//更新ret
else
{
hash[sum]=i;
}
}
return ret;
}
};
三. 矩阵区域和
3.1 题目链接:https://leetcode.cn/problems/matrix-block-sum/description/
3.2 题目分析:
本题看内容介绍较为抽象,可以把K理解为行列的扩充数,如题中所给示例:
1 2 3
4 5 6
7 8 9
此时k=1,因此answer[0][0]即为在原矩阵mat[0][0]的基础上行列各扩张一行,此时区域内的元素就包括mat[0][0],mat[0][1],mat[1][0],mat[1][1],因此和为1+2+4+5等于12.
注意:在扩充时如果超出矩阵本身界限,则超出部分不予处理。
3.3 思路讲解:
在第一篇中,我们详细讲解了二维矩阵的前缀和计算方法,因此在此处不再加以赘述,着装讲解本题思路。
具体步骤如下:
- 首先,由于本题要处理边界问题,我们首先求出行m和列n,并构建前缀和矩阵
- 在进行扩充时,注意矩阵区域和的左右边界均不可超出矩阵本身
- 前缀和矩阵dp[i][j]的求法在第一篇中有详细说明,如果存在遗忘可移步进行查看。
第一篇链接:https://blog.csdn.net/2303_81060385/article/details/144199070?spm=1001.2014.3001.5502
3.4 代码实现:
class Solution {
public:
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));//构建前缀和矩阵
vector<vector<int>> ret(m,vector<int>(n));//返回矩阵
//处理前缀和数组
for(int i=1;i<m+1;i++)
{
for(int j=1;j<n+1;j++)
{
dp[i][j]=dp[i-1][j]+dp[i][j-1]+mat[i-1][j-1]-dp[i-1][j-1];
}
}
//使用前缀和数组
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;
}
};
小结
关于前缀和算法的讲解就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!