加载中…
应用场景:区间和查询 · 差分数组
交互式动画演示,可调整参数并单步执行。
全屏打开问题:给定数组,频繁查询区间 [l, r] 的和。
前缀和是空间换时间的经典范例,也是差分的逆运算。
prefix[0] = 0
prefix[i] = nums[0] + nums[1] + ... + nums[i-1]
区间和:sum(l, r) = prefix[r+1] - prefix[l]
// 构建
int n = nums.length;
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
// 查询 [l, r] 的和(0-indexed)
int rangeSum = prefix[r + 1] - prefix[l];graph LR P0[prefix 0 = 0] --> P1[prefix 1 = 3] P1 --> P2[prefix 2 = 5] P2 --> P3[prefix 3 = 9] P3 --> P4[prefix 4 = 12] P4 --> P5[prefix 5 = 15]
以
nums = [3, 2, 4, 3, 3]为例,sum(1,3) = prefix[4] - prefix[1] = 12 - 3 = 9。
问题:和为 K 的子数组个数(LeetCode 560)。
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
count.put(0, 1); // prefix = 0 出现 1 次
int prefix = 0, ans = 0;
for (int num : nums) {
prefix += num;
ans += count.getOrDefault(prefix - k, 0);
count.merge(prefix, 1, Integer::sum);
}
return ans;
}prefix[j] - prefix[i] = k → 找之前有多少个 prefix[i] = prefix[j] - kprefix[i][j] = 左上角 (0,0) 到 (i-1, j-1) 的矩形和。
int[][] prefix = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int 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];
}
}查询 (r1,c1) 到 (r2,c2) 的和:
int sum = prefix[r2+1][c2+1]
- prefix[r1][c2+1]
- prefix[r2+1][c1]
+ prefix[r1][c1];容斥原理:大矩形 - 上方 - 左方 + 重叠部分。
差分是前缀和的逆运算:
diff[0] = nums[0]
diff[i] = nums[i] - nums[i-1] (i >= 1)
对 diff 求前缀和即可还原 nums。
问题:对区间 [l, r] 的所有元素加 val,执行 m 次操作。
int[] diff = new int[n + 1];
// 对 [l, r] 加 val
diff[l] += val;
diff[r + 1] -= val;
// 还原
int[] result = new int[n];
result[0] = diff[0];
for (int i = 1; i < n; i++) {
result[i] = result[i - 1] + diff[i];
}问题:n 个航班,bookings[i] = [first, last, seats] 表示对 [first, last] 每个航班增加 seats 个座位。
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] diff = new int[n + 1];
for (int[] b : bookings) {
diff[b[0] - 1] += b[2];
diff[b[1]] -= b[2];
}
int[] ans = new int[n];
ans[0] = diff[0];
for (int i = 1; i < n; i++) {
ans[i] = ans[i - 1] + diff[i];
}
return ans;
}| 变体 | 用途 | 示例 |
|---|---|---|
| 前缀异或 | 区间异或查询 | xor(l,r) = preXor[r+1] ^ preXor[l] |
| 前缀乘积 | 区间乘积(注意零) | 分段处理 |
| 前缀最大值 | 区间最值(静态) | max(l,r) 需 Sparse Table |
| 模前缀和 | 子数组和整除 K | (prefix[j] - prefix[i]) % k == 0 |
int[] preXor = new int[n + 1];
for (int i = 0; i < n; i++) {
preXor[i + 1] = preXor[i] ^ nums[i];
}
// 区间 [l, r] 异或
int xorLR = preXor[r + 1] ^ preXor[l];| 数据结构 | 单点修改 | 区间查询 | 区间修改 | 适用场景 |
|---|---|---|---|---|
| 前缀和 | ❌ O(n) | ✅ O(1) | ❌ | 静态数组、离线查询 |
| 差分 | ✅ O(1) | ❌ O(n) | ✅ O(1) | 批量修改、最后统一查询 |
| 树状数组 | ✅ O(log n) | ✅ O(log n) | ⚠️ 需双 BIT O(log n) | 动态单点 + 前缀查询 |
| 线段树 | ✅ O(log n) | ✅ O(log n) | ✅ O(log n) | 动态区间修改 + 查询 |
n+1),避免边界特判。count.put(0, 1) 别忘——处理从头开始的子数组。