算法精讲——前缀和:小白也能秒懂的时空魔法

15 阅读3分钟

算法精讲——前缀和:小白也能秒懂的时空魔法 ✨

📖 本文配套练习:力扣前缀和专题

🧑💻 建议边看边敲代码体验更佳哦~


🌰 从存钱罐说起——什么是前缀和?

想象你有个存钱罐📦,每天记录存入金额:

天数12345
金额25134

如果要算第3天到第5天存了多少钱,传统做法是:1+3+4=8元💰 前缀和的做法是:预先计算累计金额,这样就能快速查询任意区间的和!

// 前缀和数组计算(索引从1开始更直观)
int[] nums = {2,5,1,3,4};
int[] preSum = new int[nums.length+1];

for(int i=1; i<=nums.length; i++){
    preSum[i] = preSum[i-1] + nums[i-1]; 
}
// 得到 [0,2,7,8,11,15]

🧩 一维前缀和实战教学

例题1:基础查询(力扣303)

区域和检索 - 数组不可变

graph LR
    A[原始数组] --> B[构造前缀数组]
    B --> C{查询请求}
    C --> D[计算差值]

Java实现细节

class NumArray {
    private int[] preSum;
  
    // 初始化:O(n)时间
    public NumArray(int[] nums) {
        preSum = new int[nums.length + 1];
        for (int i = 1; i <= nums.length; i++) {
            preSum[i] = preSum[i-1] + nums[i-1];
        }
    }
  
    // 查询:O(1)时间
    public int sumRange(int left, int right) {
        return preSum[right+1] - preSum[left];
    }
}

🔍 关键点解析

  1. preSum[0] = 0 作为哨兵值,统一计算逻辑
  2. 数组索引从1开始,与天数自然对应
  3. 查询时注意right+1的边界处理

🚀 二维前缀和升级版

例题2:矩阵区域和(力扣304)

二维区域和检索 - 矩阵不可变

假设我们有如下矩阵:

1 2 3
4 5 6
7 8 9

对应的二维前缀和矩阵:

// 构造公式:
preSum[i][j] = preSum[i-1][j] 
             + preSum[i][j-1] 
             - preSum[i-1][j-1] 
             + matrix[i-1][j-1];

// 结果矩阵:
0  0  0  0
0  1  3  6
0  5 12 21
0 12 27 45

可视化推导

graph TD
    A[原点0,0] --> B[计算第一行]
    B --> C[逐行填充]
    C --> D[处理重叠区域]

Java实现

class NumMatrix {
    private int[][] preSum;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        preSum = new int[m+1][n+1];
      
        for(int i=1; i<=m; i++){
            for(int j=1; j<=n; j++){
                preSum[i][j] = preSum[i-1][j] 
                             + preSum[i][j-1] 
                             - preSum[i-1][j-1] 
                             + matrix[i-1][j-1];
            }
        }
    }
  
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2+1][col2+1] 
             - preSum[row1][col2+1] 
             - preSum[row2+1][col1] 
             + preSum[row1][col1];
    }
}

💡 高阶技巧:前缀和+哈希表

例题3:统计子数组(力扣560)

和为 K 的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1); // 初始状态:前缀和为0出现1次
        int count = 0, preSum = 0;
      
        for(int num : nums){
            preSum += num;
            // 查找之前是否存在preSum - k
            if(map.containsKey(preSum - k)){
                count += map.get(preSum - k);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);
        }
        return count;
    }
}

🔑 核心思想: 通过哈希表记录前缀和出现次数,将时间复杂度从O(n²)降到O(n)


📝 总结表格:不同场景的时空复杂度

场景暴力法时间复杂度前缀和优化后
一维区间查询O(n) per queryO(1)
二维矩阵查询O(mn)O(1)
统计子数组O(n²)O(n)

🛠️ 前缀和四步使用法

  1. 识别问题:是否涉及区间累加和?
  2. 构建preSum:注意初始化方式和索引偏移
  3. 推导公式:找出preSum之间的数学关系
  4. 处理边界:特别是二维场景的四个角点计算

🎯 现在尝试用前缀和解这道题吧:724. 寻找数组的中心下标