前缀和加Hash表解LeetCode 974&560

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;
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章