深入理解前缀和算法:原理、应用与实战

4 阅读3分钟

前言

前缀和(Prefix Sum)是一种简单却极其强大的算法技巧,它能在许多问题中显著降低时间复杂度。本文将详细介绍前缀和的概念、实现方法,并通过LeetCode例题展示其应用场景。

一、什么是前缀和?

前缀和,又称累计和,是一种预处理技术,它通过预先计算并存储数组的累计和,使得后续的区间求和查询可以在常数时间内完成。

简单来说,对于一个数组 nums,我们构造一个前缀和数组 prefixSum,其中:

prefixSum[i] = nums[0] + nums[1] + ... + nums[i-1]

或者另一种常见定义:

prefixSum[i] = nums[0] + nums[1] + ... + nums[i]

二、前缀和的基本实现

让我们看看如何在JavaScript中实现前缀和:

// 标准前缀和实现
function buildPrefixSum(nums) {
    const prefixSum = new Array(nums.length + 1).fill(0);
    for (let i = 0; i < nums.length; i++) {
        prefixSum[i + 1] = prefixSum[i] + nums[i];
    }
    return prefixSum;
}

// 使用示例
const nums = [1, 2, 3, 4, 5];
const prefixSum = buildPrefixSum(nums);
console.log(prefixSum); // [0, 1, 3, 6, 10, 15]

这种实现方式下,计算区间 [i, j] 的和可以表示为:

const rangeSum = prefixSum[j + 1] - prefixSum[i];

注意:我们一定要搞清楚前缀和的区间prefixSum[i]代表着是[0 , i]的和还是[0 , i - 1]的和,这要看自己的前缀和是怎么实现的,一定要搞清楚

三、LeetCode例题解析

例题1:560. 和为 K 的子数组 - 力扣(LeetCode)

题目描述:给定一个整数数组和一个整数k,你需要找到该数组中和为k的连续子数组的个数。

暴力解法(O(n²)):

function subarraySum(nums, k) {
    let count = 0;
    for (let start = 0; start < nums.length; start++) {
        let sum = 0;
        for (let end = start; end < nums.length; end++) {
            sum += nums[end];
            if (sum === k) {
                count++;
            }
        }
    }
    return count;
}

前缀和优化解法(O(n)):

function subarraySum(nums, k) {
    const map = new Map();
    map.set(0, 1); // 初始状态,和为0出现1次
    let prefixSum = 0;
    let count = 0;
    
    for (const num of nums) {
        prefixSum += num;
        if (map.has(prefixSum - k)) {
            count += map.get(prefixSum - k);
        }
        map.set(prefixSum, (map.get(prefixSum) || 0) + 1);
    }
    
    return count;
}

例题2: 304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)

题目描述:给定一个二维矩阵,计算其子矩形范围内元素的总和。

前缀和解法

class NumMatrix {
    constructor(matrix) {
        if (!matrix.length || !matrix[0].length) {
            this.prefix = [[]];
            return;
        }
        
        const rows = matrix.length;
        const cols = matrix[0].length;
        this.prefix = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
        
        for (let i = 1; i <= rows; i++) {
            for (let j = 1; j <= cols; j++) {
                this.prefix[i][j] = matrix[i-1][j-1] 
                    + this.prefix[i-1][j] 
                    + this.prefix[i][j-1] 
                    - this.prefix[i-1][j-1];
            }
        }
    }
    
    sumRegion(row1, col1, row2, col2) {
        return this.prefix[row2+1][col2+1] 
            - this.prefix[row1][col2+1] 
            - this.prefix[row2+1][col1] 
            + this.prefix[row1][col1];
    }
}

四、总结

前缀和是一种简单却强大的预处理技术,它通过空间换时间的方式显著提高了区间求和操作的效率。掌握前缀和不仅可以帮助我们解决许多LeetCode中的中等难度问题,还能为解决更复杂的算法问题奠定基础。

适合场景:

  • 需要频繁查询区间和
  • 数据不经常变动(否则需要更高级的数据结构如线段树)

在实际应用中,前缀和常常与其他技巧如哈希表、滑动窗口等结合使用,形成更高效的解决方案。理解前缀和的本质并能灵活运用,是算法学习中的一个重要里程碑。