什么是前缀和
前缀和(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;
};