多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

差分数组与区间加值最大值:深入解析 Cosmos 仓库中的 HackerRank Array Manipulation 解法

差分数组与区间加值最大值:深入解析 Cosmos 仓库中的 HackerRank Array Manipulation 解法 教程示例工程【免费下载链接】cosmosWorlds largest Contributor driven code dataset | Used in Quark Search Engine, OpenGenus IQ, OpenGenus Visual Project项目地址https://gitcode.com/gh_mirrors/co/cosmos点击查看免费下载导读Array Manipulation 是 HackerRank 平台上最经典的“区间更新 全局查询”问题之一给定一个初始全为 0 的长度为 n 的数组执行 m 次将区间 [a, b] 内所有元素统一加上 k的操作最终求整个数组的最大值。本文以 GitHub 加速计划 co/cosmos 仓库中 array_manipulation 题解目录 为骨架完整讲解差分数组Difference Array前缀和的 O(n m log m) 解法、其底层原理与朴素暴力法的对比并结合仓库中的前缀和与线段树实现进行纵深剖析。读完本文你将掌握区间批量更新的高效建模方式并能迁移到任意多次区间操作后查询极值的算法题与工程场景中。一、题目回顾问题描述与约束解读依据仓库内 array_manipulation/README.md 的描述You are given a list of size n, initialized with zeroes. You have to perform m queries on the list and output the maximum of final values of all the n elements in the list. For every query, you are given three integers a, b and k and you have to add value k to all the elements ranging from index a to b(both inclusive).题面翻译成工程语言即输入第一行两个整数n与m随后m行每行三个整数a b k含义为对数组下标a到b两端闭区间均包含端点的每个元素统一加上k输出所有m次操作结束后数组n个元素中的最大值。值得注意的关键约束在于数据规模HackerRank 原题中n与m均可高达10^7与2×10^5量级最终结果可能远超 32 位整数范围。因此任何对每个元素逐一遍历更新的朴素做法都会在时间与溢出两个维度上双双失败——这正是差分数组方案存在的意义下文会给出两者复杂度对比。二、暴力思路及其瓶颈为什么需要差分最直接的想法是维护一个n长的数组对每次查询用一个for循环把arr[a..b]全部加上k最后线性扫描取最大值。该做法的代价分析如下环节复杂度说明每次查询的区间更新O(b − a 1)最坏退化为 O(n)m 次查询总更新O(n·m)当 n、m 都取上限时完全不可行最终求最大值O(n)线性扫描在n ≈ 10^7、m ≈ 2×10^5时n·m ≈ 2×10^12次加法显然超时。此外若使用 32 位整型存储累加结果k 累加后很容易溢出必须使用 64 位整数。因此需要一种不在原数组上直接更新、而是通过事件标记 一次扫描间接还原最终数组的建模方式这正是差分数组Difference Array的核心思想。三、核心解法差分数组 前缀和还原3.1 差分数组思想差分数组diff与原始数组arr满足如下关系arr[i] diff[0] diff[1] … diff[i]即原数组是差分的前缀和。差分数组最大的威力在于对原数组区间 [a, b] 统一加 k等价于在差分数组上做两次单点修改diff[a] k // 前缀和从 a 开始多出 k diff[b1] - k // 前缀和到 b1 处把 k 抵消掉使影响止步于 b这样一次区间加 k就退化为两次 O(1) 的单点操作完全绕开了对原数组的逐元素遍历。3.2 仓库中的 C 实现逐行拆解仓库 array_manipulation.cpp 正是这一思想的完整落地原文件未分割行号此处按逻辑块讲解#include iostream #include vector #include algorithm int main() { int n; int m; int a; int b; int k; std::cin n m; std::vectorstd::pairint, int v;实现没有显式建立长度为 n 的差分数组而是使用vectorpairint, int以(位置, 增量)的稀疏事件列表来记录差分避免对无关下标分配空间。随后对每条查询for (int i 0; i m; i) { std::cin a b k; v.push_back(std::make_pair(a, k)); // 位置 a 处 k v.push_back(std::make_pair(b 1, -1 * k)); // 位置 b1 处 -k }这与diff[a] k; diff[b1] - k;完全等价每个查询产生两个事件。3.3 排序与扫描求最大值long mx 0, sum 0; std::sort(v.begin(), v.end()); for (int i 0; i 2 * m; i) { sum v[i].second; // 沿下标方向累加差分 → 还原当前元素真实值 mx std::max(mx, sum); // 边还原边记录历史最大值 } std::cout mx \n; return 0; }由于事件列表按位置排序sum从头到尾累加每个位置的增量恰好等于还原后的当前下标处的数组值std::max(mx, sum)则在一次线性扫描中同时完成求最大值全程只需要维护一个long类型的累加变量。因为题目保证所有元素初始为 0且最大值不会为负mx初始化为 0 是安全的若场景允许负值应初始化为LLONG_MIN。3.4 复杂度对比方案单次查询总体时间复杂度空间复杂度朴素暴力逐元素加 kO(n)O(n·m)O(n)差分数组 事件排序扫描O(1) 记录O(m log m)排序主导O(m)2m 个事件若采用显式长度 n 的差分数组并最后做一次前缀和则可进一步做到O(n m)的时间与O(n)的空间这是该问题的时间下界仓库实现用事件排序换取更省空间的方式O(m)同样属于标准且优雅的解法。四、原理解析一次扫描为何能还原最终值很多读者第一次接触时会困惑排序后的差分事件与逐次执行 m 次区间加在数学上为何等价关键在于加法满足交换律与结合律所有区间加操作对同一位置的影响是线性可叠加的最终值 所有覆盖该位置的 k 之和差分事件(a, k)与(b1, −k)把区间覆盖转化为前缀和上的阶梯前缀和从 a 开始抬升 k到 b1 被 −k 抵消恰好只在 [a, b] 内多出 k事件按位置排序后sum依次累加等价于对每个位置把差分做前缀和得到的正是该位置的最终值。因此仓库代码中边累加边取 max完全等价于先还原整个数组再线性扫描求最大值但省掉了 O(n) 的还原数组这正是本题解的精妙之处。五、相关数据结构的旁证与延伸差分数组本质上与仓库中已有的两类经典数据结构互为补充读者可在 data_structures/src 下继续对照学习5.1 前缀和Prefix Sum差分是前缀和的逆运算。仓库 prefix_sum_array/prefix_sum_subarray.cpp 与 prefix_sum_array.py 展示了前缀和数组如何把子数组求和从 O(n) 降为 O(1)res max(res, prefixSum[i] - minPrefixSum)。理解前缀和就能自然推导出差分数组的还原公式arr[i] Σ diff[0..i]。5.2 树状数组 / 线段树Fenwick Tree / Segment Tree当问题从最终求最大值升级为操作与查询交替进行例如每做一次区间加就立刻查询一次当前最大值或支持单点/区间查询静态的差分数组就不够用了此时需要支持动态更新的数据结构树状数组Fenwick Tree仓库实现见 fenwick_tree配合差分技巧可把区间加 单点查询降到 O(log n)线段树Segment Tree仓库 segment_tree/generic_segment_tree.cpp 提供泛型实现支持 lazy propagation 后可完成区间加 区间最大值查询。三者对比本题只需一次最终查询用差分数组O(nm)即达最优若查询穿插在操作之间则应升级为树状数组或线段树。六、实战要点与常见坑位闭区间边界题目明确a到b均包含端点因此差分减的位置必须是b 1而不是b否则会多扣掉一个位置的值64 位累加仓库实现中mx、sum均为long。当 n、m、k 均取上限时最终值可超过2^31 − 1切勿使用int存储累加结果事件总数m 次查询产生2m个事件扫描循环应为i 2 * m避免漏扫或越界排序稳定性同一位置可能出现多个 k 与 −k 事件它们之间的顺序不影响sum的最终值加法可交换因此无需二级排序从稀疏事件到显式差分数组若内存允许且需 O(nm) 严格最优可申请长度为n 2的long long数组逐条做diff[a] k; diff[b1] - k;后再做一次前缀和并取 max两种写法殊途同归。七、总结Array Manipulation 是一道用数学建模打败暴力枚举的教科书级题目。仓库 array_manipulation.cpp 以约 30 行的精炼代码把区间加 k转化为两个差分事件、把最终最大值转化为排序后的一次前缀和扫描在 O(m log m) 时间内完成求解充分体现了差分数组技术的核心价值。配合仓库中的前缀和与线段树/树状数组实现对照学习你将建立起静态批量更新用差分、动态交替操作用树结构的完整技术图谱从而从容应对各类区间更新类问题。赞分享教程示例工程【免费下载链接】cosmosWorlds largest Contributor driven code dataset | Used in Quark Search Engine, OpenGenus IQ, OpenGenus Visual Project项目地址https://gitcode.com/gh_mirrors/co/cosmos点击查看免费下载相关推荐Andromeda命令行使用指南从安装到高级功能的完整教程Andromeda命令行使用指南从安装到高级功能的完整教程 Andromeda是一款用C/C开发的Android应用逆向工程工具相比同类工具具有显著的性BiliTools数值分析数值计算方法与误差分析BiliTools数值分析数值计算方法与误差分析 引言多媒体下载中的数值计算挑战 在现代多媒体下载工具中数值计算扮演着至关重要的角色。BiliTools作桌面应用音视频vercel/oidc 完全指南在 Vercel Functions 中获取、交换与验证 OIDC Tokenvercel/oidc 完全指南在 Vercel Functions 中获取、交换与验证 OIDC Token vercel/oidc 是 VercelCLI后端云原生上一篇开源项目 Hall of Fame 使用教程下一篇5 分钟搞懂 pgvector 镜像标签为什么 pg15 就是 pg15一份新手快速上手指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表