ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

差分算法详解:一维二维差分数组模板与实战应用

差分算法详解:一维二维差分数组模板与实战应用 1. 这题一看就能用差分——先聊聊它到底在解决什么如果你刷过一阵子算法题或者在公司里做过跟“批量区间操作”沾边的需求大概率见过“差分”这两个字。我第一次接触差分是在一次模拟赛里一道题要求把数组的某个区间统一加上一个值循环操作几千次最后再问你整个数组变成什么样。我当时老老实实写了个双重循环结果数据一大直接超时被旁边的人一句话点醒“这种区间批量加的题你直接用差分不就行了吗”从那时候起差分算法就成了我工具箱里的常备品说它是“区间操作的瑞士军刀”一点也不夸张。差分算法本质上是一个预处理技巧通过构造一个与原数组对应的“差分数组”把原本要对连续区间逐个元素的修改压缩成对两到三个点的修改最后再用一次前缀和恢复出真实结果。它能解决的问题非常典型给你一个数组你要做大量“区间同时加某个数”的操作操作完之后才需要看最终值。这类场景在刷题里对应着各种“区间加法”“矩形区域统一更新”的题在实际工程里也能用来处理批量打标签、灰度数据累加、日志分段统计之类的需求。这篇文章聚焦两件事一维差分和二维差分。我会从最朴素的双重循环讲起说明为什么会有差分这种思路再给出可以直接抄走的模板代码最后把我在实际做题过程中踩过的坑整理成一份速查表。适合刚接触差分、只会背模板但不太理解内在逻辑的读者也适合想系统梳理前缀和与差分关系的同学。文章里我会用大量“我试过”“这样写不对”“后来改成……”这类实操视角的描述尽量把纸上谈兵的部分压到最少。顺带说一嘴搜索“差分”的时候很容易看到一个叫“差分隐私算法”的词。它跟我们这里说的差分数组完全是两码事后面我专门用一节来讲清楚这个误区的来源免得你在查资料时被带偏。2. 从暴力做法说起为什么我们需要差分2.1 一个场景复现给成绩单批量加分假设你是班长期末老师让你给一组学生的平时分做调整规则很简单每次操作给出一个区间 [l, r]表示从第 l 个学生到第 r 个学生每个人都加同样的分数。前前后后一共来了 m 次调整最后你想知道每个学生的平时分最终是多少。如果你直接用数组存分数每次调整都遍历一次区间 [l, r] 逐个加那么一次操作的时间复杂度是 O(n)m 次操作就是 O(n*m)。当学生数量是十万、操作次数也是十万时这种写法在绝大多数 OJ 上都不可能过题。我第一次写这种题时天真地以为测评机很厉害结果一组大数据直接教我做人。这个场景是差分算法最典型的应用场景也是所有区间修改类问题里最简单的一种形态。它的核心特点有三个一是一次操作只针对连续区间二是区间里的每个元素做的是相同值的加减三是中间过程不需要查询单独某个点的值只有全部操作结束之后才要最终结果。这三个特点缺一不可如果中途要频繁单点查询光靠差分还不够得配合树状数组或线段树。理解了这个边界你就知道什么时候该用差分什么时候该另请高明。2.2 核心思想把“区间修改”转化成“点修改”我们换个角度看问题。你要对区间 [l, r] 里每个数都加 c等价于什么呢设原数组为 a它的差分数组为 d定义是 d[i] a[i] - a[i-1]其中 a[0] 0。反过来a[i] 等于 d 的前 i 项之和即 a[i] d[1] d[2] ... d[i]。这两个公式是所有差分算法的地基一个是“构造”一个是“还原”它们互为逆运算。有了差分数组之后区间加该怎么做呢这就要用到“差分数组某个位置的变动会影响前缀和的尾部”这个特性。你只要让 d[l] c那么从 l 开始往后的所有前缀和都会多出 c再让 d[r1] - c那么从 r1 开始往后的前缀和又会被抵消。合在一起实际前缀和只在 [l, r] 这个范围内整体多了 c区间之外的数不受影响。这就是“把区间问题拆成左右两个边界点问题”的思路。理解了这一点你就能明白差分并不是什么高深魔法它的本质是“利用相邻两数之差来记录变化量用前缀和把变化量还原到每个位置上”。前缀和负责“聚合”差分负责“拆分”一正一反正好配成一对。很多人一开始学的时候总想着背公式结果一换题目就不知道 d[l] 和 d[r1] 到底该加还是减其实就是没把“前缀和恢复”这个过程在脑子里跑一遍。2.3 复杂度对比从 O(n*m) 到 O(nm)暴力做法每次操作需要遍历区间里的每个元素假设数组长度为 n操作次数为 m总复杂度 O(n*m)。差分做法的每次操作只改两个点复杂度 O(1)全部操作完成之后再求一次前缀和复杂度 O(n)。整体就是 O(nm)而且是严格线性的。一百万级别的数组加上一百万次操作暴力做法要跑十万亿次加法差分做法只需要几百万次。这个数量级的差距不是“快一点”的区别而是“能不能跑完”的区别。我记得自己第一次用差分优化完看到一个十万级数据瞬间跑完的时候心里最大的感叹就是数据结构不是让你会背那几行代码而是让你理解“信息该以什么形态存储和流转”。3. 一维差分实战模板、边界与为什么下标从 1 开始3.1 构造方式两种常见写法一维差分的构造有两种常见写法。第一种是最直观的先用原数组 a 初始化差分数组 d让 d[i] a[i] - a[i-1]然后在做区间操作时仍然用 d[l] c、d[r1] - c最后做前缀和还原。这种写法的好处是逻辑清晰缺点是要额外处理原数组和差分数组的对应关系。第二种写法是工程里更常用的“假设初始都是 0把原数组的每个位置也当作一次区间插入”。也就是说把数组 a 的初始值看作“在 [i, i] 区间加 a[i]”这样构造和操作就统一了。实际写代码时很多老手直接用 d[l] c、d[r1] - c 的形式把所有操作包括初始化都投进差分数组里。这样你不需要单独写一个“构造差分数组”的循环代码整体更简洁也不容易搞混。无论哪种写法最后一步都逃不掉对差分数组求前缀和得到的是修改后的原数组。这里我多说一句如果你既需要保留原数组又需要修改可以另外拷一份如果你的场景允许直接覆盖那就原地求前缀和最省事。3.2 区间加操作的标准套路单次区间加 [l, r] 加上值 c标准操作就是两条语句// 对区间 [l, r] 每个元素加 c diff[l] c; diff[r 1] - c;看起来简单到过分但这里面有两个细节值得单独强调。第一如果 r 是最后一个位置等于 ndiff[r1] 会越界所以差分数组开空间时至少要比原数组多一位最好是 n2。很多初学者在这里爆数组或者因为越界产生莫名其妙的答案错误多半就是没给 diff 留出“末尾哨兵”的位置。第二如果你用的下标从 0 开始那么区间 [l, r] 对应的是 diff[l] c 和 diff[r1] - cr1 同样可能等于 n 越界所以依然要多开一点空间。下标从 1 开始计数的传统并不是凭空来的。在差分和前缀和的体系里下标从 1 开始可以让“前缀和”的定义非常自然a[i] 前 i 项的总和a[0] 0 自然成为一个哨兵。如果你非要从 0 开始也不是不行但边界条件会多一堆 if出错概率大幅上升。我的建议是做这种题除非题目明确给了 0-index 的数组否则一律先平移成 1-index 再操作能省掉 80% 的边界烦恼。3.3 完整模板C 与 Python 双版本下面给出一个可以无脑抄的一维差分模板。以“给定长度为 n 的数组m 次区间加操作输出最终数组”为例。#include bits/stdc.h using namespace std; int main() { int n, m; cin n m; vectorlong long diff(n 2, 0); // 多开两个位置防止 r1 越界 for (int i 1; i n; i) { long long x; cin x; // 初始值也看成区间 [i, i] 加 x diff[i] x; diff[i 1] - x; } for (int i 0; i m; i) { int l, r; long long c; cin l r c; diff[l] c; diff[r 1] - c; } // 求差分数组的前缀和恢复原数组 for (int i 1; i n; i) { diff[i] diff[i - 1]; } for (int i 1; i n; i) { cout diff[i] (i n ? \n : ); } return 0; }Python 版本同样很简单。Python 的 list 没有越界保护所以尤其注意分配 n2 的长度n, m map(int, input().split()) a list(map(int, input().split())) diff [0] * (n 2) # 初始值作为 [i, i] 的加操作 for i, x in enumerate(a, start1): diff[i] x diff[i 1] - x for _ in range(m): l, r, c map(int, input().split()) diff[l] c diff[r 1] - c # 前缀和还原 for i in range(1, n 1): diff[i] diff[i - 1] # 输出 print( .join(map(str, diff[1:n 1])))这套模板我用了很久实测过很多题目核心就一条所有“在某个区间整体加一个数”的动作统一转成差分数组上两个点的修改所有初始值也当作区间操作来处理这样就不需要专门的“构造差分”环节代码心智负担小得多。注意差分数组里存的是 long long / int 取决于数据范围。起点数和操作数如果都是十万级别每次加一万最后累加可能超过 int 上限所以竞赛里我一般直接开 long long免得因为溢出白白一发 WA。3.4 问什么要用前缀和还原而不是直接输出 diff这是很多初学者最容易犯迷糊的地方。diff 数组本身存的是相邻元素的差它不直接代表最终答案。比如原数组是 [2, 5, 3]差分数组是 [2, 3, -2]直接看 diff 根本看不出原数组长什么样。只有把 diff 从头到尾累加2、235、5-23才能还原出 [2, 5, 3]。所有区间操作之所以能在差分数组上生效依赖的正是“前缀和”这种聚合方式。如果你在某道题里发现“我明明按模板写了答案却全错”不妨先停下来想一想我是不是把 diff 当成最终数组输出了这个错误我犯过不止一次后来总结出一个自查流程操作全部结束之后先肉眼检查一遍 diff 的形态再手动跑一遍前缀和确认没问题再输出。这种自查在初学阶段特别管用。4. 二维差分从“区间”到“子矩阵”的升级4.1 二维前缀和与二维差分的互逆关系一维差分解决的是“一维区间批量加”的问题二维差分解决的是“二维子矩阵批量加”的问题。这两个问题在逻辑上完全对称一维里有前缀和 差分互逆二维里也有二维前缀和 二维差分互逆。如果你已经理解了二维前缀和是怎么算的那么二维差分的构造就顺理成章。二维前缀和的定义是 s[i][j] 表示从 (1,1) 到 (i,j) 这个子矩阵内所有元素的和。它的递推公式是 s[i][j] s[i-1][j] s[i][j-1] - s[i-1][j-1] a[i][j]。这个公式里的“加两个方向减一个重叠角”的操作很多人觉得难记我却觉得这是理解二维差分的钥匙——因为二维差分构造时也用到了同样的“容斥”思想。差分数组 d 与二维前缀和互为逆运算对 d 做二维前缀和得到原矩阵 a。反过来对 a 做“相邻差分”就得到 d。如果你只想背结论可以这样记二维差分的构造是 d[i][j] a[i][j] - a[i-1][j] - a[i][j-1] a[i-1][j-1]。注意这里的符号规律和二维前缀和的容斥公式完全一致都是“加自己减上方减左方加左上角”。4.2 子矩阵加值的四个点操作现在如果我要把左上角 (x1, y1)、右下角 (x2, y2) 这个子矩阵里的所有元素都加上 v二维差分该怎么改根据二维前缀和的容斥原理操作可以总结成四个点的修改d[x1][y1] v d[x21][y1] - v d[x1][y21] - v d[x21][y21] v这个结论有个好记的说法左上角加左下角的下一行减右上角的下一列减右下角的下一行下一列加。说白了就是你覆盖了哪个区域就在那个区域的四个“外角”上做加减让二维前缀和在区域内部恰好加上 v在区域外部又恰好抵消。为什么右下角是 (x21, y21) 而不是 (x2, y2)因为前缀和是向后“扩散”的你不把越界边界堵住影响就会传播到整个右下三角。这一点和一维里 diff[r1] - c 是同一个逻辑一个管住正向传播一个管住斜向扩散。我在初学二维差分时最容易写错的就是这个右下角的符号和下标后来每次写完都会在草稿纸上画一个 3x3 的小矩阵手动模拟一遍确认无误再敲代码。4.3 二维差分代码模板二维差分的模板也很固定这里我用 Python 展示逻辑更直观。假设有一个 n 行 m 列的矩阵初始矩阵所有元素已知然后给你若干个子矩阵加操作最后输出整个矩阵。n, m, q map(int, input().split()) # 注意要开 (n2) x (m2)防止越界 diff [[0] * (m 2) for _ in range(n 2)] # 读入初始矩阵也当作对每个单点 (i,j) 的加操作 for i in range(1, n 1): row list(map(int, input().split())) for j in range(1, m 1): v row[j - 1] diff[i][j] v diff[i 1][j] - v diff[i][j 1] - v diff[i 1][j 1] v # 处理子矩阵加 for _ in range(q): x1, y1, x2, y2, v map(int, input().split()) diff[x1][y1] v diff[x2 1][y1] - v diff[x1][y2 1] - v diff[x2 1][y2 1] v # 二维前缀和还原 for i in range(1, n 1): for j in range(1, m 1): diff[i][j] diff[i - 1][j] diff[i][j - 1] - diff[i - 1][j - 1] # 输出 for i in range(1, n 1): print( .join(map(str, diff[i][1:m 1])))C 版本就是把嵌套循环原样搬过去这里不再单独写完整代码原理和 Python 一模一样的。有一点要提醒二维差分数组的大小不能吝啬我习惯直接开成 (n2, m2)就是给“越界边界”留位置。之前有朋友开成 (n1, m1)结果在 x21 或 y21 撞到边界时直接越界调了半小时才发现。4.4 二维差分的复杂度分析一次子矩阵操作改 4 个点复杂度从暴力的 O(nm) 降到 O(1)。初始化时把每个元素当作单点操作每个点改 4 个位置整体初始化 O(nm)。最后做二维前缀和恢复也是 O(nm)。总复杂度 O(nm q)q 是操作次数。这个提升比一维更可观。暴力情况下一次操作可能要遍历整个子矩阵在某些极端数据里甚至接近整个矩阵二维差分让每次操作变成常数时间所以当操作次数 q 很大时优势非常明显。工程里如果要对一个图片的某些矩形区域做亮度修正、对报表的某些单元格区块做数据调整这种“多次批量更新、最后只取结果”的场景和二维差分的适配度极高。5. 从题目到代码一个完整的实战过程复盘5.1 模拟一个“区间加法”题目我拿一道非常经典的题来完整走一遍流程。题目要求给定一个长度为 n 的初始数组可能给出初始值也可能全 0 接下来有 m 次操作每次给一个区间 [l, r] 加上一个值 c最后输出数组中每个位置的值。这题在 LeetCode 上有原题区间加法在很多 OJ 上也有变体比如“分数线调整”“库存批次变动”这类换皮题。假设题目给出初始数组全 0那连初始化的区间操作都不用做了直接差分数组全部置 0然后处理 m 次操作即可。最后求一次前缀和输出结果。这个题的演算过程很简单我就不贴全代码了重点说一下我每次写这种题时的思考顺序第一步确定是一维还是二维。题目给的是一维数组就是一维差分。第二步看下标范围。如果题目输入区间从 1 开始直接 1-index如果从 0 开始需要整体平移或者把 l、r 都加 1。我习惯平移。第三步处理初始值。如果初始值给的是一个数组就执行 n 次“单点加”操作如果题目保证初始全为 0就跳过这一步。第四步处理所有区间操作每条操作两条语句。第五步求前缀和。第六步输出注意格式。这六步是我压箱底的固定流程适用于 90% 的一维差分题。遇到二维题就把“两条语句”换成“四个点”其余思路完全一致。5.2 用实例演示从输入到输出题目的输入可能是这样的5 3 1 3 2 2 4 3 1 5 1意思是数组长度为 5初始全 0操作 1 是在 [1,3] 加 2操作 2 是在 [2,4] 加 3操作 3 是在 [1,5] 加 1。我们来走一遍差分数组的变化初始 diff 数组全 0。第一次操作 [1,3] 加 2diff[1] 2diff[4] - 2。第二次操作 [2,4] 加 3diff[2] 3diff[5] - 3。第三次操作 [1,5] 加 1diff[1] 1diff[6] - 1diff 数组长度至少 7这里不会越界。操作结束后diff 数组是diff[1] 3 diff[2] 3 diff[3] 0 diff[4] -2 diff[5] -3 diff[6] -1然后求前缀和a[1] diff[1] 3a[2] 3 3 6a[3] 6 0 6a[4] 6 (-2) 4a[5] 4 (-3) 1最终数组是 [3, 6, 6, 4, 1]。你可以拿暴力循环验证一遍结果是一样的。这个过程我建议你多手算几组算多了自然就理解为什么差分数组的最后一个“负值”会精确地把区间外的前缀和抵消掉。5.3 二维实例子矩阵统一加值的手算过程再看一个二维例子。一个 3x3 矩阵初始全 0。操作为把子矩阵 (1,1) 到 (2,2) 加 5。初始 diff 全 0操作后的四个点修改是diff[1][1] 5 diff[3][1] - 5 diff[1][3] - 5 diff[3][3] 5然后做二维前缀和得到的矩阵是5 5 0 5 5 0 0 0 0完全符合预期。就这个手算过程我当年在草稿本上画了好几张每次把四个点的效果逐格叠加最终看到“右上和左下扩散被精确抵消”的那一刻才算真正理解了二维差分的容斥本质。再说一个更复杂点的例子给你初始矩阵1 2 3 4再操作把 (1,1) 到 (1,2) 这一行加 10。也就是第一行两个元素都加上 10。操作后的 diff 修改是diff[1][1] 10 diff[2][1] - 10 diff[1][3] - 10 diff[2][3] 10把初始值也按单点操作写进 diff 后做二维前缀和还原得到11 12 3 4第一行加 10第二行不受影响。这类“只有局部区域受影响”的例子最能检验你是否真的理解了四个点的作用范围。5.4 实际刷题心得先把暴力写出来这里说一个我的个人习惯遇到没把握的差分题我第一版总是先写一个暴力版本哪怕它绝对超时。理由有两个一是暴力版本的逻辑一定是对照着题目描述逐字翻译的不容易被边界条件带偏二是拿它跟差分版本对拍能在最短时间内验证差分代码的正确性。很多同学一上来就追求“最优写法”结果边界写错调试大半天还不知道错在哪其实先写个暴力反而更省时间。对拍的方法很简单构造一堆随机小数据分别跑暴力版本和差分版本比较输出。如果有一组不一致就手动把那一组数据拎出来逐步打印 diff 的过程。我靠这个办法抓出过很多莫名其妙的 bug比如某个区间右边界写成了 r 而不是 r1比如某处用了 int 导致溢出。对拍这个习惯强烈建议每个刷算法题的读者都养成。6. 常见问题与排查技巧实录6.1 高频错误速查表我整理了一份自己在带人和被带的过程中反复遇到的高频错误清单直接做成表格方便排查。错误现象常见原因解决办法输出结果整体“平移”或错位下标从 0 开始但没做平移或前缀和循环起点不对统一改用 1-indexdiff 数组多开一位越界崩溃或结果随机混乱对 r1 / x21 / y21 的越界没有预留空间一维开 n2二维开 (n2, m2)首尾正确但中间全错求前缀和时覆盖了 diff 数组导致后续操作失效操作全部完成后再求前缀和中间不要同时读写二维只有一个子矩阵加值结果四个角都变了右上或左下的符号写反或者右下角坐标写错在草稿纸画 3x3 矩阵手推四个点的前缀和int 溢出得到负数或截断值操作值累加超出 int 范围差分数组直接用 long long忘记把初始值写进差分数组只处理了 m 次操作初始数组丢失初始值作为 n 次单点加操作统一处理输出前没有求前缀和直接把 diff 当作答案混淆差分数组与原数组的关系先做一遍前缀和再输出这是差分算法的最后一步这张表我建议你收藏起来每次写差分题提交前对着上面这些坑快速过一遍。我自己的“提交前检查四连”是数组空间够不够左右边界是 r 还是 r1前缀和求了没数据范围爆不爆 long long这四个问题过完基本能躲开 90% 的常见错误。6.2 边界条件到底该怎么把握差分算法里的边界条件往往是最容易翻车的地方。一维的边界是“r 等于 n 时diff[n1] 是否存在”二维的边界是“x2 等于 n 或 y2 等于 m 时x21 或 y21 是否存在”。选 n2、m2 而不是 n1是为了给最极端的那次越界留出位置。另外还要注意一个容易忽略的细节如果你把 diff 数组中的某个负下标误写成了正下标或者把 x21 写成了 y21编译器不会报错但结果会跑偏。这类问题光靠肉眼很难看出来最好就是构造一个包含全部边界情况的数据比如操作覆盖整个数组、只覆盖一个点、覆盖末尾区间全部验证一遍。我在实际做题时会专门为边界写一组测试用例而不是只测题目样例。6.3 差分和线段树、树状数组怎么选很多人学完差分后会问那还有树状数组和线段树它们之间是什么关系我的回答是差分解决的是“离线批量修改、最后统一查询”的问题线段树和树状数组解决的是“修改和查询交替出现”的问题。如果你只有一次最终查询差分一定是首选因为它最简单、常数最小、代码量最少。但如果你在中间过程不断需要查询某个点的当前值差分就无能为力了因为差分数组的每个中间状态都不能直接反映真实值必须每次重新求前缀和。这时候可以用树状数组维护差分数组实现对单点查询的 O(log n) 支持或者直接上线段树支持区间加、区间查询。最近我在看题目讨论时看到有评论把差分红黑树、差分约束这类词放在一起比较其实它们是不同层次的概念。差分约束是图论里的最短路模型不是差分数组的进阶别混为一谈。学习算法时画清楚“工具的使用边界”比背更多模板更重要这也是我一直跟人强调的一点。7. “差分隐私”到底是什么它跟差分算法是一回事吗7.1 这个热词为什么会让人混淆搜“差分”相关内容时“差分隐私算法”这个关键词经常跳出来。我第一次看到时也很疑惑以为是差分算法在隐私计算领域的新应用。后来查过资料才明白差分隐私Differential Privacy是密码学和数据隐私保护领域的概念它跟这里的差分数组算法没有直接关系。差分隐私的核心思路是向查询结果中注入精心设计的随机噪声使得任何一个具体个体的数据是否存在于数据集中对最终输出的影响都控制在一个很小的范围内。换句话说它要保护的是“单个样本的隐私”方法是通过“模糊化”让攻击者无法通过对比两次查询结果的差异来推断出某个个体的信息。这里的“差分”指的是“有我没我产生的差异”不是数组的差分操作。这个误会很常见因为中文都带“差分”两个字。但本质上一个是一维/二维数组的区间操作优化工具一个是数据发布时防止个体信息泄露的隐私保护框架两者从理论基础到应用场景都不同。如果你是被“差分隐私”这个词吸引来的建议单独去了解隐私保护相关的资料别用差分数组的思路去套它。7.2 理解这两个“差分”的不同才能真正理解各自的价值我举一个例子帮助理解。假设一个班级的平均成绩是 85 分你想对外公布这个数据但又不想被人反推出某个同学是不是考了高分。差分隐私的做法是在公布“85 分”之前给 85 加上一个随机噪声比如变成 83 或 87。这样一来外人看到两次统计结果时无法分辨数值变化到底是因为某位同学的成绩变动还是因为噪声本身在浮动。这个思路跟差分数组完全不同。差分数组是通过“记录相邻变化”来还原真实值而差分隐私是通过“故意引入误差”来隐藏真实值。一个追求精确一个追求模糊一个用于优化计算效率一个用于保护数据安全。搞清楚了它们各自的边界你在查资料时就不会再被标题里的“差分”带偏了。如果你对隐私保护方向感兴趣倒是可以沿着差分隐私的思路继续了解“本地差分隐私”“全局差分隐私”“隐私预算 epsilon”这些概念它们在现代数据采集和联邦学习里经常被提到。但那又是另一个话题了和本文的差分算法并不是同一条技术栈。8. 写在最后差分思想还能用到哪我在实际工程里见过不少场景可以套用差分思想。比如给一批用户打标签按某个连续时间段批量加标记再比如处理日志统计时对某个时间窗口累加事件计数最后统一输出各时间点的事件量。这些场景的共同特征是“批量修改连续的一段最后才取结果”这时候用差分数组能节约大量计算资源。我个人做这类题最大的体会是不要死记模板而要反复手算“区间加后的前缀和轨迹”。把 diff[2] 3 之后前缀和从哪个位置开始变到哪个位置被抵消每一步都手算一遍比看任何教程都有用。做完二三十道题你会发现这类题基本长得一个样代码写起来几乎不用动脑。当然差分算法也有它天然的边界它不适合中间频繁查询、不适合非连续区间操作、不适合区间值不一致的修改。如果遇到这几种情况请果断换树状数组或线段树。理解了这些边界你才算真正“掌握”了差分算法而不是只会抄代码。最后再分享一个小技巧如果一道题你第一眼判断出是差分但写完后死活不对先别急着怀疑算法本身把 diff 数组的中间过程打印出来对照着手算几行。绝大多数时候问题出在某个下标的 1 或 -1 上而不是思路错了。这个“打印中间结果”的习惯帮我节省过无数调试时间希望你也能用起来。
返回列表