前置知识

前缀和是一个数组的某下标之前的全部元素之和(包括下标自身)。
前缀和是一种重要的预处理,能够降低算法的时间复杂度。
前缀和的公式:sum[i] = sum[i-1] + arr[i] ; sum是前缀和数组, arr是内容数组。拥有前缀和数组后, 我们可以在O(1)的时间复杂度内求出区间和。
[i, j]的区间和公式:interval [i, j] = sum[j] - sum[i - 1]

例题

560. 和为 K 的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 :::info 输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。 :::

思路

解法一
先构造一个前缀和,将数组的每个元素的前缀和都记录在一个数组中。
然后二次遍历该数组,计算前缀和之差,当结果与目标和相等时,计数加一。

  1. var subarraySum = function(nums, k) {
  2. const len = nums.length;
  3. if (!len) return 0;
  4. const sums = [];
  5. sums[0] = 0;
  6. for (let i = 1; i <= len; i++) {
  7. sums[i] = sums[i-1] + nums[i-1];
  8. }
  9. let count = 0;
  10. for (let i = 0; i < len; i++) {
  11. for (let j = i+1; j < len+1; j++) {
  12. let sum = sums[j] - sums[i];
  13. if (sum === k) {
  14. count++;
  15. }
  16. }
  17. }
  18. return count;
  19. };

解法二
在解法一中,我们需要O(n2)的时间复杂度以及额外数组,这与直接暴力求解并无太大差异。
为了解决这个问题,我们可以引入哈希表。
首先,我们遍历数组,每经过一个元素,计算前缀和,利用哈希表查找是否存在当前前缀和 - 目标值k
如果有,则计数增加,将当前前缀和加入到哈希表中。

  1. var subarraySum = function(nums, k) {
  2. const len = nums.length;
  3. if (!len) return 0;
  4. const map = new Map();
  5. map.set(0, 1);
  6. let pre = 0, count = 0;
  7. for (let item of nums) {
  8. pre += item;
  9. if (map.has(pre - k)) {
  10. count += map.get(pre - k)
  11. }
  12. map.set(pre, (map.get(pre) || 0) + 1);
  13. }
  14. return count;
  15. };