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