一、题目描述
题意是给一个数组nums,和一个目标值k,让我们找到子数组内元素之和为k的子数组数量。值得注意的是,题目还给出了测试数据的规模,最大的数组规模为,而一般的OJ一秒内能承受的运算次数为,所以这个题时间复杂度为及以上的算法是肯定通过不了的,所以当题目给定输入规模时,我们就要对提出的算法的时间复杂度的级别心里有数了。
二、解法一:暴力法
最笨的暴力法解决问题的逻辑,其实就是把题意用计算机语言实现一遍罢了。题目让找所有子数组的和,那么我们就先用两层循环找到所有的子数组subarray{i, j},然后再对i到j内的元素进行累加,时间复杂度为,空间复杂度为,C++代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
int n = nums.size(), res = 0;
for(int i = 0; i < n; ++i)
{
for(int j = i; j < n; ++j)
{
int tempSum = 0;
for(int k = i; k <= j; ++k)
{
tempSum += nums[k];
}
if(tempSum == k) ++res;
}
}
return res;
}
};
三、解法二:优化的暴力法
解法一肯定过不了,所以考虑对解法一进行优化。在解法一中,对于subarray{i, j}中的每一个值,我们每次都老老实实得一个个加起来,做了大量重复的工作。简单思考后我们发现,只要得到子数组subarray{0, i}和sum_i子数组subarray{0, j}的和sum_j,那么子数组subarray{i, j}的和sum就等于sum_j - sum_i + nums[i](其中0 <= i <= j < n),而且所有subarray{0, i}的和的值都可以通过预处理原数组在时间内得到。
根据以上逻辑,可以得到优化的暴力法:定义一个新数组pre[n],其中pre[i]代表子数组subarray{0, i}的和,遍历一遍原数组就可以得到数组pre。然后还是要通过两重循环找到每一个子数组subarray{i, j},不过这一次subarray{i, j}的和可以用pre[j] - pre[i] + nums[i]在时间内得到,所以整个算法的时间复杂度为,空间复杂度为,C++代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
int n = nums.size(), res = 0, sum = 0;
vector<int> pre(n);
for(int i = 0; i < n; ++i)
{
sum += nums[i];
pre[i] = sum;
}
for(int i = 0; i < n; ++i)
{
for(int j = i; j < n; ++j)
{
if(pre[j] - pre[i] + nums[i] == k)
++res;
}
}
return res;
}
};
但是根据我们最开始的分析,这个的方法还是过不了,所以还需要继续优化。
四、解法三:还记得Leetcode第一题两数之和的优化方法吗?
先思考一下解法二,我们在解法二中搞了一个pre数组,把问题转化为了找所有pre[j] - pre[i] == k的数目(先不用管加不加nums[i]的事,这些细节问题可以在写具体程序的时候再仔细考虑),为了实现这个目的我们用两重循环遍历数组。
那么怎么优化呢?突然发现这个式子pre[j] - pre[i] == k好眼熟啊,我们是不是在哪里见过啊?对!就是leetcode第一题两数之和!回想到两数之和是让我们找到所有nums[i] + nums[j] == k的情况(这里不管是加是减逻辑上是一样的),用暴力法也是,我们当时是用什么方法优化成的来着?对!就是哈希表!想到这里这个题的优化逻辑其实就清楚起来了。
我们需要找到所有pre[j] - pre[i] == k(其中0 <= i <= j < n)的情况,当我们遍历到pre[j]时,暴力法会在i = 0~j的范围内一个一个找符合要求的值,需要的时间为,但其实我们可以把j之前的所有pre[i]的结果都存在一个哈希表里,这样我们就可以在时间内找到我们需要的值了,所以算法总的时间复杂度为。
总结一下:先得到pre数组,然后定义一个哈希表,在C++中我们可以使用unordered_map,哈希表中每一个元素的key代表pre[i]的值,value代表pre[i]出现的次数,因为pre数组可能有重复的元素。每次遍历到j时,只需要看有几个pre[j] - k在哈希表里即可,显然哈希表需要添加一个初始化值为make_pair(0, 1),为了正确处理pre[j] == k的情况,C++代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
int n = nums.size(), sum = 0, res = 0;
vector<int> pre(n);
unordered_map<int, int> mp;
mp[0] = 1;
for(int i = 0; i < n; ++i)
{
sum += nums[i];
pre[i] = sum;
}
for(int i = 0; i < n; ++i)
{
if(mp.count(pre[i]-k) != 0)
res += mp[pre[i]-k];
mp[pre[i]] += 1;
}
return res;
}
};
该方法时间复杂度为,空间复杂度为。
进一步分析发现,其实没必要对先把pre数组计算出来,只需要动态计算sum的值并把结果存到哈希表里就行了,所以去掉初始化pre数组的开销,时间复杂度可以优化为,空间复杂度也为,C++代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
unordered_map<int, int> mp;
mp[0] = 1;
int n = nums.size(), sum = 0, res = 0;
for(int i = 0; i < n; ++i)
{
sum += nums[i];
if(mp.count(sum - k) != 0)
res += mp[sum - k];
mp[sum] += 1;
}
return res;
}
};