1. 前缀和是什么
对于一个数组,若其前缀和数组为,则有:
。
由前缀和特点,我们可以推导:
,
特殊的一点是:
,
利用前缀和,可以快速解决一下连续区间计算问题,而再配上哈希表,则有希望进一步降低时间复杂度。
2. 小实牛刀
题目描述:
给你一个整数数组
nums和一个整数k,请你统计并返回 该数组中和为k的连续子数组的个数。
如果使用模拟法,也就是挨个计算数组中不同区间中数字之和,统计和为 k的数量,则时间复杂度为。
下面我们尝试使用前缀和来优化,根据题目,我们需要找出数组中和为 k 的连续子数组,即:
,
我们使用前缀和来进一步替换:
,
也就是说,前缀和数组中,后项减前项差值为k的各组符合题意,那么我们可以首先计算前缀和数组,然后依次求其中两项差值,差为k的个数,即为题解。这种遍历算法很常见,其时间复杂度为仍为,与原算法相比,虽然使用前缀和避免了频繁的累加,但是时间复杂度仍然没有量级上的变化。
我们进一步思考,既然题目仅统计个数,那我们可以先把前缀和不同值的个数统计出来,然后找出他们差值为k的一组,就能直接计算出计算出个数,避免遍历。存储值的数量,容易想到哈希表。需要注意的是,差为k,必须是后项减前项,那我们可以一边计算前缀和,一边统计数量,避免后面的前缀和前介入运算。至此,算法轮廓基本完成,伪代码如下:
for (int num : nums) {
计算到当前项的前缀和
根据当前前缀和,查表找出满足差为k的前缀和数量,总数量累加该数量
更新哈希表,将当前前缀和存入
}
返回总数量
需要注意的是,每一项自身也算一个区间,例如[i,i],因此为了能计算到自身这种区间,哈希表默认需要存入(0,1)的一项。
具体代码实现如下:
public int subarraySum(int[] nums, int k) {
int result = 0;
int preSum = 0;
Map<Integer, Integer> cache = new HashMap<>();
cache.put(0, 1);
for (int num : nums) {
preSum += num;
result += cache.getOrDefault(preSum - k, 0);
cache.compute(preSum, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer k, Integer v) {
return null == v ? 1 : v + 1;
}
});
}
return result;
}
3. 总结
若题目中出现子数组,并给出连续子数组和限制条件,以求解,或有其他可分解成该类问题的其他条件,则应考虑前缀和,如果题目中仅要求统计数量,可使用哈希表进行优化。
如果你看到最后,欢迎点个赞。