差分数组与前缀和
差分数组 = 前缀和的逆运算。区间 [l, r] += k 这种 diff[l] += k; diff[r+1] -= k,
直觉 — 前缀和的逆运算
把”求和”和”差分”想成函数:
| 操作 | 公式 | 类比 |
|---|---|---|
| 前缀和 | pre[i] = pre[i-1] + a[i] |
积分 ∫ |
| 差分 | diff[i] = a[i] - a[i-1] |
求导 d/dx |
两个互为逆运算: 先差分再前缀和 = 原数组。这不是巧合,就是离散版本的微积分基本定理。
所以一个想法立刻浮现: 既然修改”原数组的一段区间”很贵(
为什么是两个端点
设原数组
变了: 加了 , 没变,所以 。 变了: 没变, 加了 ,所以 。 - 中间
的 : 前后都加了 ,差不变。
整段区间的修改,只在两个端点留下痕迹。这就是差分省时间的根。
动画 — 跟着端点走
下面的动画用长度 8 的数组演示三次区间加: [1,4]+3、[2,6]+2、[5,7]+1。每点一次”下一步”,看一对端点(绿色 +、红色 -)在 diff 上落子;六次落子完成后,做一次前缀和还原出最终结果数组。
重点观察: 修改阶段从来不碰中间,只在 l 和 r+1 两个位置变化;还原阶段一次扫描搞定。
模板 — Python
1 | |
三个易错点:
diff开n+1长度,省掉if r+1 < n的判断。- 闭区间
[l, r]对应diff[r+1] -= k;如果题目给的是半开区间[l, r),那就是diff[r] -= k。 - 如果原数组不是全 0,先对原数组做一遍差分得到初始
diff,再叠加操作。
与前缀和的对偶
1 | |
一个是”只查不改”,一个是”只改不查”。两者对偶,选哪个看题目要的是查询多还是修改多。 修改和查询交替进行的场景,才上线段树。
二维差分 — 矩形批量加
把一维的两端点扩展到矩形的四个角。对子矩形
1 | |
还原用二维前缀和: a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + diff[i][j]。模板题: LC 2536. 子矩阵元素加 1。
记忆口诀: 左上 +, 右下外两侧 -, 右下外的对角 +。和二维前缀和的容斥公式严格对偶。
经典题 — 1109. 航班预订统计
n 个航班,给定
bookings[i] = [first, last, seats]表示从第first到last班(闭区间)每班预订seats个座位。返回每班的总座位数。
直接套模板,注意题目下标从 1 开始:
1 | |
复杂度
其他必刷:
- LC 370. 区间加法 — 模板裸题
- LC 1094. 拼车 — 上下车看成 ±,扫一遍判超载
- LC 1854. 人口最多的年份 — 一维扫描线即差分
- LC 1674. 使数组互补的最少操作次数 — 差分 + 枚举,体会”看变化量”的威力
记忆法
一句话: 改区间,改两端;还原靠前缀和。
三个对仗:
| 维度 | 前缀和 | 差分 |
|---|---|---|
| 解决问题 | 区间查询 | 区间修改 |
| 操作 | 累加 | 取差 |
| 微积分类比 | 积分 | 求导 |
判题口诀:
- 只查不改 → 前缀和
- 只改后查(可以离线) → 差分
- 改查交替(必须在线) → 线段树
看到”对一段区间统一加/减”再加”最后一次性输出/查询”,条件反射写 diff[l] += k; diff[r+1] -= k。错不了。