算法:前缀和

发布于:2025-09-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

📘 前缀和算法模板(Java)

本文整理了 8 大常见前缀和算法模板,方便直接复用。


1) 一维前缀和(基础)

思路:构造 prefix[i] = nums[0..i-1],查询区间和 O(1)。

public class PrefixSum1D {
    private long[] prefix; // prefix[i] = sum(nums[0..i-1])

    public PrefixSum1D(int[] nums) {
        int n = nums.length;
        prefix = new long[n + 1];
        for (int i = 0; i < n; i++) {
            prefix[i + 1] = prefix[i] + nums[i];
        }
    }

    // 区间和 nums[l..r]
    public long rangeSum(int l, int r) {
        if (l > r) return 0;
        return prefix[r + 1] - prefix[l];
    }
}
  • 构造 O(n),查询 O(1)

2) 前缀和 + 哈希表(统计子数组个数)

思路:哈希表记录前缀和出现次数,统计目标区间数量。

public int subarraySumEqualsK(int[] nums, int k) {
    Map<Long, Integer> count = new HashMap<>();
    count.put(0L, 1);
    long prefix = 0;
    int res = 0;
    for (int x : nums) {
        prefix += x;
        res += count.getOrDefault(prefix - k, 0);
        count.put(prefix, count.getOrDefault(prefix, 0) + 1);
    }
    return res;
}
  • 经典题:560. Subarray Sum Equals K
  • 时间/空间:O(n)

3) 差分数组(前缀和逆运算)

思路:区间更新时用差分,最后统一恢复。

public int[] applyRangeUpdates(int n, int[][] updates) {
    // updates: {l, r, val}
    long[] diff = new long[n + 1];
    for (int[] up : updates) {
        int l = up[0], r = up[1], val = up[2];
        diff[l] += val;
        if (r + 1 < diff.length) diff[r + 1] -= val;
    }
    int[] result = new int[n];
    long cur = 0;
    for (int i = 0; i < n; i++) {
        cur += diff[i];
        result[i] = (int) cur;
    }
    return result;
}
  • 典型题:370, 1094, 1109
  • 复杂度:O(n + m)

4) 二维前缀和(矩阵)

思路pre[i][j] 表示 (0,0) 到 (i-1,j-1) 的和,查询用容斥。

public class PrefixSum2D {
    private long[][] pre;

    public PrefixSum2D(int[][] mat) {
        int m = mat.length, n = (m == 0 ? 0 : mat[0].length);
        pre = new long[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                pre[i][j] = mat[i-1][j-1] + pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1];
            }
        }
    }

    public long sumRegion(int r1, int c1, int r2, int c2) {
        int i1 = r1 + 1, j1 = c1 + 1, i2 = r2 + 1, j2 = c2 + 1;
        return pre[i2][j2] - pre[i1-1][j2] - pre[i2][j1-1] + pre[i1-1][j1-1];
    }
}
  • 典型题:304, 1314
  • 构造 O(mn),查询 O(1)

5) 前缀和 + 滑动窗口 / 双指针

思路:前缀和计算区间和,窗口控制长度(数组需非负)。

public int minSubArrayLen(int target, int[] nums) {
    int n = nums.length, left = 0, right = 0;
    long sum = 0;
    int ans = Integer.MAX_VALUE;
    while (right < n) {
        sum += nums[right++];
        while (sum >= target) {
            ans = Math.min(ans, right - left);
            sum -= nums[left++];
        }
    }
    return ans == Integer.MAX_VALUE ? 0 : ans;
}
  • 典型题:209
  • 复杂度:O(n)

6) 前缀和 + 单调队列

思路:维护单调递增前缀和索引,用于最短子数组 ≥ K(含负数)。

public int shortestSubarrayAtLeastK(int[] A, int K) {
    int n = A.length;
    long[] pre = new long[n + 1];
    for (int i = 0; i < n; i++) pre[i+1] = pre[i] + A[i];

    Deque<Integer> dq = new ArrayDeque<>();
    int ans = Integer.MAX_VALUE;
    for (int i = 0; i <= n; i++) {
        while (!dq.isEmpty() && pre[i] - pre[dq.peekFirst()] >= K) {
            ans = Math.min(ans, i - dq.pollFirst());
        }
        while (!dq.isEmpty() && pre[i] <= pre[dq.peekLast()]) {
            dq.pollLast();
        }
        dq.addLast(i);
    }
    return ans == Integer.MAX_VALUE ? -1 : ans;
}
  • 典型题:862
  • 复杂度:O(n)

7) 前缀和 + 异或(前缀 XOR)

思路xor[l..r] = pre[r+1] ^ pre[l]

public int[] xorQueries(int[] arr, int[][] queries) {
    int n = arr.length;
    int[] pre = new int[n + 1];
    for (int i = 0; i < n; i++) pre[i+1] = pre[i] ^ arr[i];
    int m = queries.length;
    int[] ans = new int[m];
    for (int i = 0; i < m; i++) {
        int l = queries[i][0], r = queries[i][1];
        ans[i] = pre[r+1] ^ pre[l];
    }
    return ans;
}
  • 典型题:1310, 1442
  • 复杂度:O(n + q)

8) 前缀和 + 前缀最值

Kadane(经典 DP)

public int maxSubArrayKadane(int[] nums) {
    int cur = nums[0], best = nums[0];
    for (int i = 1; i < nums.length; i++) {
        cur = Math.max(nums[i], cur + nums[i]);
        best = Math.max(best, cur);
    }
    return best;
}

前缀最小值法

public int maxSubArrayPrefix(int[] nums) {
    long prefix = 0, minPrefix = 0, best = Long.MIN_VALUE;
    for (int x : nums) {
        prefix += x;
        best = Math.max(best, prefix - minPrefix);
        minPrefix = Math.min(minPrefix, prefix);
    }
    return (int) best;
}
  • 典型题:53, 643
  • 复杂度:O(n)

✨ 总结口诀

  • 区间和 → 前缀数组
  • 子数组个数 → 前缀和 + 哈希表
  • 区间更新 → 差分
  • 矩阵问题 → 二维前缀和
  • 区间长度优化 → 滑动窗口 / 单调队列
  • 异或问题 → 前缀 XOR
  • 子数组最大/最小 → 前缀和 + 前缀最值

网站公告

今日签到

点亮在社区的每一天
去签到