← 返回题目列表

什么是差分数组?如何 O(1) 完成区间批量加?

高频 中等 第 12 / 20 题 更新于 2026/07/28
差分数组区间更新前缀和

简化版

差分数组是前缀和的逆运算,专治「多次区间批量加、最后统一查询」。diff[i] = a[i] - a[i-1]。要给区间 [l, r] 的每个元素都加 val,只需两次修改:diff[l] += valdiff[r+1] -= val,O(1)。所有区间操作做完后,对 diff前缀和就还原出最终数组。把 m 次区间更新从 O(mn) 降到 O(m + n)

详细版

// 原数组 a,差分数组 diff[i] = a[i] - a[i-1](diff[0] = a[0])
int[] diff = new int[n + 1];           // 多留一位防 r+1 越界

// 区间 [l, r] 全部加 val:只改两个端点,O(1)
void rangeAdd(int l, int r, int val) {
    diff[l] += val;
    diff[r + 1] -= val;                 // r+1 处减掉,抵消 r 之后的影响
}

// 所有操作做完后,前缀和还原出结果数组
int[] restore() {
    int[] res = new int[n];
    res[0] = diff[0];
    for (int i = 1; i < n; i++)
        res[i] = res[i - 1] + diff[i];  // 前缀和还原
    return res;
}
  • diff[l] += val:从 l 开始,后面都被抬高 val。
  • diff[r+1] -= val:到 r+1 处抵消,使影响只作用于 [l, r]
  • 前缀和还原:res[i] = res[i-1] + diff[i],把差分变回原值。

完整版教学

一、差分是前缀和的逆运算

前缀和:由原数组算「累加和」。差分:由原数组算「相邻差」,diff[i] = a[i] - a[i-1]。两者互逆——对差分数组求前缀和,就还原出原数组:

res[i] = diff[0] + diff[1] + ... + diff[i]
       = a[0] + (a[1]-a[0]) + (a[2]-a[1]) + ... = a[i]   (中间全抵消)

这个「差分 ↔ 前缀和」的互逆关系是理解差分的钥匙:前缀和把「区间查询」变 O(1),差分把「区间更新」变 O(1),它俩是一对。

二、核心:区间加只需改两个端点

差分的威力在于「区间批量加」只要改两个位置。要给 [l, r] 每个元素加 val:

  • diff[l] += val:在差分数组的 l 处 +val。还原(前缀和)时,从 l 开始的所有元素都会被抬高 val。
  • diff[r+1] -= val:在 r+1 处 -val。还原时,从 r+1 开始又被压低 val,正好抵消——于是 val 的影响被精确限制在 [l, r] 区间内。

一次区间加只改两个数,O(1);不管区间多长都一样。这是差分最核心的性质。

三、为什么这样能限定影响范围

从「前缀和还原」的角度看:res[i] = diff[0] + ... + diff[i]diff[l] += val 意味着「从 l 往后每个 res 都多加了 val」;diff[r+1] -= val 意味着「从 r+1 往后每个 res 又减了 val」。两个效果叠加:

  • i < l:两个改动都没影响,res[i] 不变。
  • l <= i <= r:只受 +val 影响,res[i] 加了 val。✓
  • i >= r+1:+val-val 抵消,res[i] 不变。✓

正好实现了「只给 [l, r] 加 val」。记住 [l] += val, [r+1] -= val 这个定式

四、适用场景:多次区间更新 + 最后查询

差分的典型使用模式是:

  1. 先积累所有区间更新:m 个操作,每个 rangeAdd(l, r, val) 都是 O(1),共 O(m)。
  2. 最后一次前缀和还原:O(n) 得到最终数组。

总共 O(m + n),而朴素做法「每次区间加都遍历区间」是 O(mn)。所以差分适合「更新很多、且更新集中在前面、查询在后面」的场景。注意:差分不适合「更新和查询交替进行」(每次改完立刻要查),那种用树状数组/线段树。

五、经典应用

  • 航班预订统计(LeetCode 1109):每个预订是一个区间加座位数,差分 + 还原。
  • 拼车(LeetCode 1094):上下车看成区间 [start, end) 加减乘客,判断是否超载。
  • 区间加法会议室人数统计统计每个时刻的在线人数等,凡是「大量区间 ± 某值,最后看每个位置的值」都用差分。

六、和前缀和、树状数组的关系

工具擅长复杂度
前缀和静态数组、区间查询预处理 O(n)、查询 O(1)
差分区间批量更新 + 最后查询更新 O(1)、还原 O(n)
树状数组/线段树更新与查询交替单点/区间改查都 O(log n)

前缀和管「查」、差分管「改」,两者互逆、场景互补;既要频繁改又要频繁查,才上树状数组/线段树。

七、从公式证明到手算闭环

这道题成立的核心是:diff[i] 记录当前位置相对前一位置的增量,区间起点开启影响,终点后一位关闭影响。先明确每个数组槽或哈希键的数学含义,代码中的下标偏移才不是死记硬背。

diff[0] = a[0]
diff[i] = a[i] - a[i-1]
add(l,r,v): diff[l] += v; if r+1<n: diff[r+1] -= v
a[i] = a[i-1] + diff[i]

带数字推演:零数组长度 5,对 [1,3] 加 2 后 diff 为 [0,2,0,0,-2],前缀恢复为 [0,2,2,2,0]。手算时同时列出原数组、辅助状态和本轮新增答案,能够直接发现端点偏一、初始化遗漏以及更新顺序错误。

核对维度本题结论
正确性依据diff[i] 记录当前位置相对前一位置的增量,区间起点开启影响,终点后一位关闭影响
复杂度每次区间更新 O(1),最终还原 O(n),额外空间 O(n)
关键边界右端点后一位可能等于 n;差分适合多次更新后统一还原,不适合更新与区间查询交错的在线场景

记忆钩子:不要先背代码,先说清辅助状态“代表哪一段”;公式只是把重叠部分消掉或把边界影响传播出去。

八、实现边界与测试策略

实现时最需要警惕的是:右端点后一位可能等于 n;差分适合多次更新后统一还原,不适合更新与区间查询交错的在线场景。这不是语法细节,而是决定算法是否仍满足题目语义的前提。

提交前应分别验证:

  • 空数组或最小合法规模,确认哨兵位置和初始化。
  • 查询或更新紧贴左、上边界,确认没有访问负下标。
  • 查询或更新紧贴右、下边界,确认“终点后一位”不会越界。
  • 包含 0、负数或重复前缀的样例,确认频次与取模语义。
  • 大数输入,确认累计和、乘积或答案数量的整数类型足够。

如果需求从离线变成在线,或从单次恢复变成更新查询交错,原方法可能不再合适。此时应根据操作类型改用树状数组、线段树、二维结构或其他能维护动态状态的数据结构,而不是强行沿用静态前缀模型。

九、常见误区与追问

  • 误区:diff[r] 减 v 就能结束区间影响。 闭区间 [l,r] 应在 r+1 处关闭影响。
  • 误区:差分更新后原数组立即可查。 需要做一次前缀累加才得到最终值。
  • 误区:差分可以高效回答任意区间和。 裸差分擅长区间更新,不直接提供在线区间查询。
  • 追问:为什么两个端点就够? 前缀恢复会把起点增量向后传播,负增量在 r+1 抵消传播。
  • 追问:更新和查询交错怎么办? 使用树状数组或带懒标记的线段树。
  • 追问:能否在原数组上构造? 可以从右向左做 a[i]-=a[i-1],避免覆盖尚未使用的前值。

十、加强记忆

差分数组是前缀和的逆运算(diff[i]=a[i]-a[i-1],对差分求前缀和还原原数组)。核心:区间 [l,r] 加 val 只需 diff[l]+=val; diff[r+1]-=val,O(1)——l 处抬高、r+1 处抵消,把影响限定在区间内。多次区间更新后一次前缀和还原,总 O(m+n)(朴素 O(mn))。适合「大量区间加、最后查询」(航班预订、拼车);改查交替用树状数组/线段树。前缀和管查、差分管改,互逆互补。