当前位置: 首页 > news >正文

插旗子法-告别TLE超时!一文看懂算法利器——“差分数组”(附详细图解与代码)

插旗子法

写目录

  • 插旗子法
    • 一、 痛点:被“区间修改”支配的恐惧
    • 二、 核心思想:“插旗子”法
    • 三、 图解差分数组
    • 四、 代码模板
    • 五、 总结与避坑指南

一、 痛点:被“区间修改”支配的恐惧

在刷算法题时,我们经常会遇到这样一种场景:

给你一个长度为N NN的数组(初始全为 0),接着有Q QQ次操作,每次操作要求你把区间[L, R]内的所有元素都加上一个数C CC。最后问你数组里的每个元素变成了多少?

小白的直觉做法(暴力循环):

// q 次操作for(inti=0;i<q;i++){// 每次把区间 [L, R] 里的数都 +1for(intj=L;j<=R;j++){arr[j]++;}}

后果是什么?
如果N = 200 , 000 N = 200,000N=200,000Q = 200 , 000 Q = 200,000Q=200,000。最坏情况下,你的程序要跑20 万 × 20 万 = 400 亿 20万 \times 20万 = 400亿20×20=400亿次循环!提交上去绝对是红色的Time Limit Exceeded (TLE)

那么,有没有什么办法,能让我们不用一遍遍地去循环修改中间的数字呢?
这就是差分数组大显身手的时候了!


二、 核心思想:“插旗子”法

理解差分数组,我们先抛开枯燥的数学公式,想象一个**“刷墙”**的场景:

假设有一面 10 米长的墙,老板让你把第 3 米到第 7 米刷成红色(也就是区间[3, 7]加上 1)。

  • 普通人的做法:拿着刷子走到第 3 米,刷一米,走到第 4 米,刷一米…一直刷到第 7 米。(耗时耗力)
  • 聪明人的做法(差分):在第 3 米处插一个小旗子写着“从这里开始刷 (+1)”,在第 8 米处(7的下一米)插一个小旗子写着“到这里停止刷 (-1)”

当你把所有刷墙任务的“旗子”都插好后,最后雇一个人从第 1 米走到最后,手里拿着计算器,看到+1就加上,看到-1就减去,他手里计算器的数字,就是这一段墙应该被刷的次数!


三、 图解差分数组

我们把刚刚的“插旗子”翻译成数组操作:

我们要对区间[3, 7]进行+1操作。
我们不去循环,只做两件事:

  1. diff[3] += 1(开头 +1)
  2. diff[8] -= 1(结尾的下一位 -1,因为区间在 7 就结束了)

操作完后,我们的diff差分数组变成了这样:

下标123456789
diff数组00+10000-10

见证奇迹的时刻:如何还原成真实的数组?
我们只需要从左到右扫一遍,做一个累加(求前缀和)

  • 位置 1:0
  • 位置 2:0 + 0 = 0
  • 位置 3:0+ 1= 1(受到开头旗子的影响,变成 1 啦!)
  • 位置 4:1 + 0 = 1
  • 位置 5:1 + 0 = 1
  • 位置 6:1 + 0 = 1
  • 位置 7:1 + 0 = 1
  • 位置 8:1- 1= 0(受到结束旗子的影响,变回 0 啦!)
  • 位置 9:0 + 0 = 0

你看!通过只修改两个端点,加上最后的一次累加,我们完美实现了区间[3, 7]全部+1的效果!


四、 代码模板

这里提供一个标准 Java 模板。时间复杂度直接从O ( N × Q ) O(N \times Q)O(N×Q)降维打击到了O ( N + Q ) O(N + Q)O(N+Q)

publicclassDifferenceArrayDemo{publicstaticvoidmain(String[]args){intlength=10;// 数组长度// 差分数组的长度最好开大一点,防止 R+1 导致数组越界int[]diff=newint[length+2];// 模拟 3 次区间操作// 操作1:区间 [2, 5] 加上 1add(diff,2,5,1);// 操作2:区间 [4, 7] 加上 2add(diff,4,7,2);// 操作3:区间 [1, 3] 减去 1add(diff,1,3,-1);// 最后:通过前缀和还原真实数组int[]result=newint[length+1];intcurrent=0;for(inti=1;i<=length;i++){current+=diff[i];// 累加当前的标记result[i]=current;// 这就是位置 i 经过所有操作后的真实值System.out.print(result[i]+" ");}}/** * 差分数组核心方法:区间修改 * 在 [L, R] 范围内加上 val */publicstaticvoidadd(int[]diff,intL,intR,intval){diff[L]+=val;// 起点加上 valdiff[R+1]-=val;// 终点的后一位减去 val}}

五、 总结与避坑指南

  1. 什么时候用差分数组?
    • 只要题目出现**“频繁对一个区间进行加减操作”,且“最后才询问数组的最终状态”**,无脑用差分数组!
  2. 差分和前缀和是好兄弟:
    • 差分前缀和的逆运算。
    • 差分负责把O ( N ) O(N)O(N)的区间修改变成O ( 1 ) O(1)O(1)
    • 前缀和负责最后扫尾,把差分数组还原成真实数据。
  3. 注意数组越界:
    • 因为我们要操作diff[R + 1],所以初始化差分数组时,大小一定要比题目给定的最大范围多加 2,避免ArrayIndexOutOfBoundsException
  4. 局限性:
    • 差分数组属于离线算法。也就是说,你必须把所有的“修改操作”都搞完,最后才能统一看结果。如果你在操作的中间就想问“某个数字现在是多少”,差分数组就不太行了(这种场景需要用到高级数据结构:线段树树状数组)。

作者的话:
以前遇到计数或区间修改问题,我总想着使用 HashMap 或者直接 for 循环遍历计数,结果总是被大数据量教做人(TLE 或 MLE)。直到学会了差分数组,才感叹算法设计的巧妙!把对一条线的修改,转化为对两个点的标记,这不纯纯编程里的“降维打击”吗!

以前遇到计数或区间修改问题,我总想着使用 HashMap 或者直接 for 循环遍历计数,结果总是被大数据量教做人(TLE 或 MLE)。直到学会了差分数组,才感叹算法设计的巧妙!把对一条线的修改,转化为对两个点的标记,这不纯纯编程里的“降维打击”吗!

http://www.jsqmd.com/news/813556/

相关文章:

  • 靠谱多模型聚合平台供应商盘点 为AI项目匹配靠谱合作伙伴
  • 扣图操作方法完全指南:一键去背景,从小白到高手只需3步
  • 手把手教你用PyTorch 0.4.1复现D-LinkNet道路分割(附完整代码与数据集)
  • 智慧巡检-基于改进RT-DETR的道路交通小目标检测系统(含UI界面、yolov8、Python代码、数据集)基于 PyTorch 和 PyQt5 RT-DETR 或 YOLOv8
  • ComfyUI-WanVideoWrapper完整指南:从零开始掌握AI视频生成神器
  • EvaDB:用SQL驱动AI,重塑数据库应用开发范式
  • 6AV6648-0AC11-3AX0操作面板
  • PB9实战:数据窗口的强大能力与复杂应用之一(以医保门诊发票打印为例)
  • VS Code 修改 C++ 标准同时修改错误检测标准
  • 基于DuckyClaw框架的智能家居设备开发:从原理到量产实践
  • 苍穹外卖 项目记录 第六天
  • srcdoc属性怎么内嵌HTML_iframe直接注入【技巧】
  • EDA数据管理难题的通用解法:规则引擎驱动的设计对象抽象
  • 深耕高性价比多模型聚合平台赛道,这些企业值得重点关注
  • 扼流圈GNSS监测站
  • SkillsOver:AI代理安全审计工具,防御HTML注入与供应链攻击
  • -g安装和不使用-g安装的区别,本地开发环境和生产环境
  • 安培匝数抵消法:精准测量大直流偏置下微小电流纹波的工程实践
  • 图片怎么去水印?2026图片去水印方法实测 + 好用工具推荐
  • 3步解锁全功能:Cursor Free VIP智能加速方案指南
  • [Java+阿里云 SMS + Redis] 阿里云短信服务使用
  • 金融机器学习实战:从特征工程到投资组合优化的完整工具库解析
  • 深入Android系统源码:screencap命令背后,SurfaceFlinger如何“画”出一张图?
  • DeepSeek模型观测从黑盒到透明:手把手搭建Grafana可观测性看板(含Prometheus采集全链路)
  • 从嵌入式到FPGA:思维转变、实战入门与软硬件协同设计指南
  • Next.js国际化实战:i18next与next-i18next完整配置指南
  • 【干货】SFP连接器选型指南:笼子与连接器怎么配?光口速率、散热结构、压力配合技巧全解析 | VOOHU 沃虎电子
  • 掌握RCTCOE与12种核心模式,解锁高效AI提示词工程实战
  • 从零到一:我的Elsevier期刊LaTeX投稿实战与避坑指南
  • 粒子物理模拟的GPU加速与NLO计算优化