『力扣题解』前缀和

639 阅读2分钟

什么是前缀和

前缀和(Prefix Sum)是一种用于快速计算数组元素区间和的算法。它通过预先计算数组的累积和,并将这些累积和存储在另一个数组中,从而使得计算任意区间的和变得非常高效。

假设有一个数组 arr,其元素依次为 [a[0], a[1], a[2], ..., a[n-1]],那么前缀和数组 prefixSum 的元素定义如下:

cssCopy code
prefixSum[0] = a[0]
prefixSum[1] = a[0] + a[1]
prefixSum[2] = a[0] + a[1] + a[2]
...
prefixSum[i] = a[0] + a[1] + ... + a[i]

通过预先计算前缀和数组,我们可以用 O(1) 的时间复杂度来计算任意区间 [i, j] 的和,即 sum[i, j] = prefixSum[j] - prefixSum[i-1](当 i > 0 时,否则直接取 prefixSum[j])。这种方式避免了在每次求区间和时都需要遍历数组的开销,从而提高了计算效率。

这是常用的求前缀和代码片段:

const n = nums.length
const sum = [0]

for (let i = 1; i <= n; i++) {
    sum[i] = sum[i - 1] + nums[i - 1]
}
  • 第一个元素为0,等于前0个值之和。
  • 第二个元素是nums[0],等于前1个值之和。
  • 第三个元素是nums[0] + nums[1],等于前2个值之和...

前缀和 + 哈希表

前缀和算法和哈希表可以结合起来解决一些特定的问题,特别是那些涉及子数组和、连续区间和等问题。通过使用哈希表来存储前缀和的值及其对应的出现次数,可以在一些情况下进一步优化算法的时间复杂度。

用Map或Set的本质是一样的,只是用Map能够:

  • 方便记录下标(key=前缀和,val=下标),在计算长度时用得到。
  • 方便统计次数(key=前缀和,val=次数),该前缀和到目前为止一共出现了多少次,在计算子数组个数时用得到。

523. 连续的子数组和

给定一个整数数组,找到具有最大和的连续子数组。

我们可以先计算前缀和数组,然后遍历前缀和数组,同时使用Set来记录每个前缀和%k的值。如果当前前缀和%k的值在集合中可以找到,那么这之间的数之和可以整除k。

var checkSubarraySum = function (nums, k) {
  const n = nums.length;
  const prefixSum = [0];
  const s = new Set();
  for (let i = 1; i <= n; i++) {
    prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
  }
  // 只要模的值相等即可,并且i - 2确保了区间长度至少是2
  for (let i = 2; i <= n; i++) {
    s.add(prefixSum[i - 2] % k);
    if (s.has(prefixSum[i] % k)) {
      return true;
    }
  }

  return false;
};

525. 连续数组

给定一个二进制数组,找到具有相等数量的0和1的最长连续子数组的长度。

将原数组中的0替换为-1,然后计算前缀和数组。使用哈希表来存储前缀和及其第一次出现的位置。遍历前缀和数组,如果某个前缀和在哈希表中已经出现过,说明这两个位置之间的子数组具有相等数量的0和1,然后计算子数组的长度。

var findMaxLength = function (nums) {
  nums = nums.map((i) => (i === 0 ? -1 : 1));
  const n = nums.length;
  const sum = [0];
  const m = new Map();
  let ans = 0;
  for (let i = 1; i <= n; i++) {
    sum[i] = sum[i - 1] + nums[i - 1];
  }
  for (let i = 0; i <= n; i++) {
    if (m.has(sum[i])) {
      ans = Math.max(ans, i - m.get(sum[i]));
    } else {
      m.set(sum[i], i);
    }
  }
  return ans;
};

560. 和为K的子数组

给定一个整数数组和一个目标整数K,求子数组和等于K的个数。

遍历数组,计算当前的前缀和,并将前缀和保存到哈希表中。对于每个位置,我们检查是否存在一个之前的位置的前缀和,使得当前的前缀和减去之前的前缀和等于K。这样可以通过查找哈希表来快速找到满足条件的子数组。

var subarraySum = function (nums, k) {
  const n = nums.length;
  const sum = [0];
  const m = new Map();
  let ans = 0;
  for (let i = 1; i <= n; i++) {
    sum[i] = sum[i - 1] + nums[i - 1];
  }
  for (let i = 0; i <= n; i++) {
    const x = sum[i] - k;
    if (m.has(x)) {
      ans += m.get(x);
    }
    // 在map中设置前缀和出现的次数
    if (m.has(sum[i])) {
      m.set(sum[i], m.get(sum[i]) + 1);
    } else {
      m.set(sum[i], 1);
    }
  }
  return ans;
};