LeetCode 560. 和为K的子数组
问题描述
解决思路
方法一 朴素暴力法
分析本文题目可知,我们首先要找到数组中所有连续子数组的可能情况,之后对每一种可能求和,然后和k的值进行比较,如果两者相等,那么最后的统计数目就要加1。
为了实现这个目标,我们可以执行以下几个步骤:
- 确定数组左端的端点i
- 确定数组右端的端点j
- 从[i,j]中逐个选择k,求[i,k]之间的和最左子数组和
使用这种方法的时间复杂度为O( n 3 n^3 n3)
方法二 将子数组求和时间复杂度优化到O(n)
刚才暴力法中的第三步求子数组和其实是可以优化的,令
S
i
S_{i}
Si表示前
i
i
i个数字的和,那么
S
[
i
,
j
]
=
S
j
−
S
i
−
1
S_{[i, j]}=S_{j} - S_{i-1}
S[i,j]=Sj−Si−1
于是,我们可以在代码中间记录一个sum值,来优化这个步骤,具体代码如下
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int numLen = nums.size();
int count=0;
for(int i=0;i<numLen;i++){
int sum=0;
for(int j=i;j<numLen;j++){
sum+=nums[j];
if(sum==k) count++;
}
}
return count;
}
};
该方法的时间复杂度为O( n 2 n^2 n2)
方法三 前缀和 + 哈希表优化
上个方法的时间复杂度还是有些高,我们可不可以继续优化呢?当然是可以的。我们的目标,是要找到
S
[
i
,
j
]
=
=
k
S_{[i,j]}==k
S[i,j]==k的所有子数组和的个数,根据刚才的公式,我们可以得到:
S
[
i
,
j
]
=
S
j
−
S
i
−
1
=
k
S_{[i, j]} = S_{j} - S_{i-1}=k
S[i,j]=Sj−Si−1=k
S
i
−
1
=
S
j
−
k
S_{i-1} = S_{j} - k
Si−1=Sj−k
由于数组的前缀和我们可以在O(n)的时间复杂度内计算得到,我们可以根据
S
j
S_{j}
Sj和
k
k
k的值直接反推出需要的前缀和
S
i
−
1
S_{i-1}
Si−1,我们可以将每个前缀和都保存在哈希表中,这样,我们就可以在O(1)的时间复杂度内搜索到对应的前缀和。如果我们找到某个前缀和在哈希表中,那我们就可以让总个数加上该前缀和的个数。
注意,我们在实现代码前需要考虑边界条件,如果数组中的第一个数就和k值相等,那么我们上述的思路可能会出问题,因此,我们需要在一开始将{0, 1}这个键值对加入到哈希表中,这样就可以使上述的公式正确。
代码如下:
class Solution{
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int sum=0;
int count = 0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
if(mp.find(sum-k)!=mp.end()){
count+=mp[sum-k];
}
mp[sum]+=1;
}
return count;
}
};