前綴和加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;
    }
};

 

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