【数据结构】前缀和详解

前缀和是一种强大的算法技巧,常用于快速处理一维或多维数组的区间求和问题。通过预先计算数组的前缀和,能够在后续查询中显著减少时间复杂度,从而提升算法效率。本文将详细介绍前缀和的概念、应用场景,并通过JavaScript代码示例进行讲解。

一、前缀和的概念

1. 什么是前缀和?

前缀和(Prefix Sum)指的是数组中从第一个元素到当前元素之间所有元素的累加和。具体来说,给定一个数组nums,它的前缀和数组prefix定义为:

prefix[i]=nums[0]+nums[1]+…+nums[i−1]prefix[i] = nums[0] + nums[1] + … + nums[i-1]prefix[i]=nums[0]+nums[1]+…+nums[i−1]

例如,对于数组nums = [1, 2, 3, 4, 5],它的前缀和数组为:

prefix=[0,1,3,6,10,15]prefix = [0, 1, 3, 6, 10, 15]prefix=[0,1,3,6,10,15]

注意这里prefix[0]为0,这是为了便于后续区间计算。

2. 前缀和的作用

前缀和的主要作用是高效地计算数组任意区间内的元素之和。假设要计算数组nums中索引从ij的元素和,可以通过以下公式快速求得:

sum(i,j)=prefix[j+1]−prefix[i]sum(i, j) = prefix[j+1] - prefix[i]sum(i,j)=prefix[j+1]−prefix[i]

这种方法能够将区间求和的时间复杂度从 O ( n ) O(n) O(n)降低到 O ( 1 ) O(1) O(1),极大地提高了计算效率。

二、前缀和的应用场景

1. 一维数组的区间求和

前缀和的经典应用之一是解决一维数组的区间求和问题。在没有前缀和的情况下,计算区间和需要遍历数组的所有元素,时间复杂度为 O ( n ) O(n) O(n)。而通过预处理前缀和数组,可以在 O ( 1 ) O(1) O(1)的时间内完成区间和的计算。

2. 二维数组的区域求和

在二维数组中,前缀和同样可以应用于计算任意子矩阵的元素和。通过构造二维前缀和数组,可以在常数时间内得到子矩阵的和,极大地提高了算法的效率。

3. 滑动窗口问题

前缀和在滑动窗口问题中也有广泛应用。例如,求解给定长度的连续子数组的最大和或最小和,前缀和能够快速计算每个窗口内的元素和,并结合其他算法进行优化。

三、前缀和的JavaScript实现

1. 一维前缀和的实现

我们首先来看如何在JavaScript中实现一维前缀和。假设有一个数组nums,我们可以通过如下代码构建它的前缀和数组prefix

function buildPrefixSum(nums) {
    let prefix = new Array(nums.length + 1).fill(0);
    for (let i = 1; i <= nums.length; i++) {
        prefix[i] = prefix[i - 1] + nums[i - 1];
    }
    return prefix;
}

let nums = [1, 2, 3, 4, 5];
let prefix = buildPrefixSum(nums);
console.log(prefix);  // 输出 [0, 1, 3, 6, 10, 15]

在这个示例中,prefix数组比nums多一个元素,prefix[0]为0,表示前0个元素的和。通过这个前缀和数组,可以快速计算任意区间的和。

2. 通过前缀和进行区间求和

利用上面构建的前缀和数组,我们可以轻松计算任意区间的和。例如,计算nums数组中索引2到4之间的元素和:

function rangeSum(prefix, i, j) {
    return prefix[j + 1] - prefix[i];
}

console.log(rangeSum(prefix, 2, 4));  // 输出 12

这里,通过prefix[j+1] - prefix[i]快速得到了从索引2到4的元素和,即3 + 4 + 5 = 12

3. 二维前缀和的实现

接下来,我们来看二维前缀和的实现。二维前缀和用于计算矩阵中任意子矩阵的元素和。首先,我们构建一个二维前缀和数组prefix,如下所示:

function build2DPrefixSum(matrix) {
    let m = matrix.length, n = matrix[0].length;
    let prefix = Array.from(Array(m + 1), () => new Array(n + 1).fill(0));
    
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            prefix[i][j] = matrix[i - 1][j - 1]
                + prefix[i - 1][j]
                + prefix[i][j - 1]
                - prefix[i - 1][j - 1];
        }
    }
    return prefix;
}

let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
let prefix2D = build2DPrefixSum(matrix);
console.log(prefix2D);

在这个示例中,prefix[i][j]表示从矩阵左上角到位置(i-1, j-1)的所有元素的和。通过预处理这个二维前缀和数组,可以快速计算任意子矩阵的和。

4. 通过二维前缀和计算子矩阵的和

有了前缀和数组后,我们可以用以下公式计算任意子矩阵的和,设矩阵的左上角为(r1, c1),右下角为(r2, c2)

sum(r1,c1,r2,c2)=prefix[r2+1][c2+1]−prefix[r1][c2+1]−prefix[r2+1][c1]+prefix[r1][c1]sum(r1, c1, r2, c2) = prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1]sum(r1,c1,r2,c2)=prefix[r2+1][c2+1]−prefix[r1][c2+1]−prefix[r2+1][c1]+prefix[r1][c1]

对应的JavaScript代码如下:

function rangeSum2D(prefix, r1, c1, r2, c2) {
    return prefix[r2 + 1][c2 + 1]
        - prefix[r1][c2 + 1]
        - prefix[r2 + 1][c1]
        + prefix[r1][c1];
}

console.log(rangeSum2D(prefix2D, 1, 1, 2, 2));  // 输出 28

在这个示例中,我们计算了矩阵matrix中从位置(1, 1)(2, 2)的子矩阵的和,即5 + 6 + 8 + 9 = 28

四、前缀和的优势和注意事项

1. 优势

前缀和最显著的优势是它将区间求和操作的时间复杂度从 O ( n ) O(n) O(n)降低到 O ( 1 ) O(1) O(1),这在处理大量查询时尤其有效。此外,前缀和在很多算法问题中都可以作为一种优化技巧,帮助提高算法的整体效率。

2. 注意事项

在使用前缀和时,需要注意以下几点:

  • 空间复杂度:前缀和数组需要额外的空间存储,通常会比原始数组多一个元素,二维前缀和数组则需要更多空间。因此,在空间有限的情况下需要谨慎使用。
  • 前缀和的初始化:在构建前缀和数组时,初始值的设置非常关键,通常我们会将prefix[0]prefix[0][0]初始化为0,以便于后续的区间计算。

五、总结

前缀和是一种简洁而高效的算法技巧,通过预处理数组或矩阵的前缀和,可以显著加快区间求和的速度。无论是一维数组还是二维矩阵,前缀和都能在多种场景中发挥重要作用。本文详细介绍了前缀和的概念、应用场景以及如何在JavaScript中实现前缀和,希望这些内容能够帮助你更好地理解和应用前缀和算法。

推荐我的相关专栏:


在这里插入图片描述

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter-Lu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值