974. 和可被 K 整除的子数组
题目:
给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
示例:
输入:A = [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]
思路:
遇到区间题,我首先想到的就是区间DP,区间DP的复杂度是O(n^2),这道题A.length<=30000,n^2=9*1e8>1e8,使用这种方法是超时的。由此也可以衍生出来,区间DP的适用方位是数组长度比较短,并且区间DP优化的是判断的时间复杂度,比如博客解最长回文子串https://blog.csdn.net/qq_31965925/article/details/106356841,使用区间DP能够将判断回文这一操作的时间复杂度从O(n)降到O(1)。
当我们遇到区间问题,并且区间数量比较大的时候,我们要想到一种思路:枚举左端点,去找符合条件的右端点。
找右端点的方式有很多,要保证其复杂度是O(logn),这样总复杂度为O(nlogn)就可以过
回到这道题,解题步骤分为下面几步:
1.计算前缀和
定义一个数组sum:sum[i]就代表A[0,i]区间的和
2.我们想要判断区间[l,r]的和 % K == 0
即(sum[r]-sum[l-1])%K==0 -> (sum[r]%K) - (sum[l-1]%K)==0 -> sum[r]%K = sum[l-1]%K
就是从l开始,要找到sum[r]取模等于sum[l-1]取模数值的r的个数
我们可以使用一个哈希表Map,modKCount[i]就代表 全部区间%K==i的个数
为了可以在后面寻找r时,直接使用这个哈希表,我们需要在遍历一个区间后,就减掉当前这个区间对应的map值
3.注意:由于数组中有负数,负数的取模将不满足上面的推导,所以一律将他们的模数转为正数
x对k取模=(x%k+k)%k
代码
class Solution {
public:
int subarraysDivByK(vector<int>& A, int K) {
int n = A.size();
map<int,int> modKCount;
if(n==0) return 0;
//1.先计算前缀和
vector<int> sum(n,0);//sum[i]就代表A[0,i]区间的和
//vector<int> modKCount(K,0);// modKCount[i]就代表sum[i]%K==i的个数
sum[0] = A[0];
//对负数求模 (x%k+k)%k 对正数也不会有影响
modKCount[(sum[0]%K+K)%K] ++;
for(int i=1; i<n; i++)
{
sum[i] = sum[i-1] + A[i];
int mod = (sum[i] % K+K)%K;
modKCount[mod] ++;
}
//枚举左端点,去找右端点
//要找到一个区间[l,r]的和 % K == 0
//即(sum[r]-sum[l-1])%K==0 -> (sum[r]%K) - (sum[l-1]%K)==0 -> sum[r]%K = sum[l-1]%K
//就是从l开始,要找到sum[r]取模等于sum[l-1]取模数值的r的个数
//直接使用 modKCount[ sum[r] ] ,这个个数也包括了区间[0,l-1]之间的个数,需要在处理的时候慢慢减掉
int count = 0;
//左端点为0
count += modKCount[0];
modKCount[(sum[0]%K+K)%K] -- ;
for(int i=1; i<n; i++)
{
int mod = (sum[i-1] % K+K)%K;
count += modKCount[mod];
modKCount[(sum[i]%K+K)%K] --;
}
return count;
}
};
560. 和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
思路:
前缀和加Hash表
使用一个数组sum存储前缀和 sum[i]表示区间[0,i]之间所有的数之和
我们遍历左端点l,想要去找右端点r,满足[l,r]的和==k个数
即sum[r] - sum[l-1] = k -> sum[r] = sum[l-1]+k
即我们想要找到一个端点r, [0,r]的和 = [0,l-1]的和+k
那么我们就使用一个Hash表记录下来sum[i]的个数
为了保证r>=l,我们每次遍历完后i,需要将sum[i]区间对于count的贡献减掉,因为我们找到的r不能够等于i
也就是说,如果我们找到一个区间[0,r] 这个区间是在l前面的,是不行的。
更新的技巧:前面cmap[sum[i]]++; 后面就cmap[sum[i]]--;
代码
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n=nums.size();
if(n==0) return 0;
int count = 0;
// 先计算前缀和
vector<int> sum(n,0);
map<int,int> cmap;//存储<sum,count>
sum[0] = nums[0];
cmap[nums[0]]++;
for(int i=1; i<n; i++)
{
sum[i] = sum[i-1] + nums[i];
cmap[sum[i]]++;
}
//遍历左端点,去找右端点
int s=0;
for(int i=0; i<n; i++)
{
count+=cmap[k+s];
cmap[sum[i]]--;
s+=nums[i];
}
return count;
}
};