假设你管理着全国最大的快递分拣中心。你不用一个个拆开包裹来数数,因为你的仓库天然是分层的:总仓管全国,大区分仓管一片省份,市级中转站管一个城市,街道站点管最末端的具体地址。客户问“华东区今天运了多少件”,你只需要翻出华东分仓的账本即可,不用把每个仓库的包裹都倒出来数。这套“分层账本”的管理哲学,正是数据结构线段树的核心思想——把一个大区间不断对半拆分成小区间,每个节点保存一个小片区的汇总信息。 线段树(Segment Tree)是一种二叉树,它的每个节点对应一个区间:根节点管整个数组,叶子节点管单个元素,中间节点管一片连续下标。无论是查询某段区间的总和、最大值,还是对一段区间统一修改,它都能在 O(log n) 的时间里给出答案。本篇文章将带你从零理解线段树的构建、查询、修改、懒传播,以及动态开点、离散化、线段树合并与持久化等进阶优化。学完之后你会发现,“把大问题拆成小账本,查账时只翻相关的账页”这一直觉,几乎能解释线段树的一切操作。
对 AI 从业者而言,线段树是处理“范围统计”“滑窗聚合”“动态极值追踪”的高性能利器。在推荐系统特征工程、时序异常检测、大规模数据分析管道中,理解它的高效查询机制,能帮你设计出更低延迟、更省计算的模型服务,也是算法面试中绕不开的硬核考点。
可视化演示
交互展示线段树构建过程、区间查询遍历路径与懒传播延迟标记机制,理解O(log n)区间操作原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
线段树结构 每个节点表示一个区间 叶子节点对应单元素 node[l,r] = node[l,mid] + node[mid+1,r] 二分构建O(n)
线段树概述
假设你现在是快递公司的总调度。总仓管全国,一张大地图被划分为几个大区,每个大区再划分为城市、街道、站点。因为账本是按“片区”逐级登记的,所以任何一次关于“某片区域”的查询都不需要翻遍所有快递单。线段树要解决的核心问题,正是如何用一棵二叉树来组织这种“分层账本”。
线段树的每个节点对应一个“区间”。根节点管整个数组区间 [0, n-1],将区间一分为二给左右孩子,左孩子管左半边,右孩子管右半边;往下不断对半切分,直到每个叶子恰好管一个元素。由于每次拆分都取中点,整棵树天然是接近完全二叉树的形态,我们用数组从上到下、从左到右存储即可。
为什么说它是“完全二叉树”?因为每个非叶节点总是恰好有两个孩子,且所有叶子都出现在最后两层。这种规则的树形结构带来一个巨大的好处:可以不用指针,直接用数组下标。设根节点下标为 1,节点 p 的左孩子是 2p,右孩子是 2p+1,就像公司给每个分仓编号一样规律清晰。
回到快递的例子:总仓的“华东账本”被贴成两半——“华东东区”和“华东西区”,每次查询只要按片区逐级索引导航,就能迅速定位到具体哪本账。下标的编号规律让这台“账本机器”只靠一个数组就能运转,这就是线段树概述里最重要的直觉。
- 求出中点用整数除法计算区间中点,将区间切成尽量均匀的两段。
- 左孩子区间区间左半边交给左孩子
2p,包含从l到mid的所有下标。 - 右孩子区间区间右半边交给右孩子
2p+1,包含从mid+1到r的所有下标。
构建线段树
分拣中心的账本不是凭空出现的,它需要从最末端的街道站开始逐级汇总:每个街道站上报当日的包裹数,市级中转站把下属街道站相加,大区把下属中转站相加,最后总仓得到全国总量。这个过程天然是自底向上的汇总。线段树的构建也是如此——从叶子到根,逐层合并统计信息。
用程序来模拟这个过程,采用的是递归建树:从根节点 (1, 0, n-1) 出发,如果当前节点对应区间左右端点相同,说明到达叶子,直接把原始数组的值写入;否则计算 mid,递归构建左子树和右子树,等两个孩子都构建完,再把孩子的值合并到父节点上。
构建的复杂度是 O(n),因为每个节点只会被访问一次。8 个元素的数组会构建出 15 个节点(4 层),n 个元素则最多需要 4n 左右的数组空间——这比直接建一棵完全二叉树的 2n 更保险,因为递归划分会产生一些“空闲”的节点槽位。
这个过程就像街道站层层上报汇总表:每个节点存储的是它管辖片区的“总量账本”。构建完毕之后,整棵线段树就成了一份自下而上维护好的完整账目,任何区间查询都可以在这棵树上直接“查账”。
max、min、gcd 等满足结合律的运算,同样可以构建对应的线段树。- 构建左子树递归调用
build(2p, l, mid),返回左半个区间的统计值。 - 构建右子树递归调用
build(2p+1, mid+1, r),返回右半个区间的统计值。 - 合并统计结果父节点的值 = 左右孩子之和,完成区间汇总。
区间查询
客户打来电话:“我想知道下标 2 到 5 的包裹总数。”作为调度员,你不会拆开每一件包裹去数,而是看一眼账本树:[2,5] 可以被完整拆成 [2,3] 和 [4,5] 两个子区间,而这两个子区间恰好都对应树上的某个节点,直接翻出这两本账相加即可。
区间查询的递归逻辑只有两条规则:
- 完全覆盖:如果当前节点区间
[l,r]完全落在查询区间[ql,qr]内,直接返回该节点的值,不再往下递归。
- 部分重叠:否则计算
mid,根据查询区间的边界决定进入左孩子、右孩子还是两边都进,把孩子的返回值相加。
为什么这样做很快?因为线段树每下降一层,区间长度就减半,最多只需要遍历 O(log n) 层。每一层虽然可能访问两个孩子,但总体访问节点数仍然控制在 O(log n) 的级别,这也是区间查询能“秒回”的原因。
回到快递比喻:整个“账本树”中,查询区间被切成少数几个完整的分区账本,其余的账本根本不用翻开。这种“只翻必要账页”的智慧,就是线段树一切高效的起点。
log2 n,每层最多访问常数个节点,因此总代价是 O(log n)。- 完全覆盖分支当前节点的区间被查询区间完全包住,说明该节点的账本就是答案的一部分。
- 递归下探否则求
mid,左半区间有重叠就进左孩子,右半区间有重叠就进右孩子,收集两边结果。 - 层数上界树的高度是
log2 n,每层最多访问 2 个节点,所以总复杂度为O(log n)。
单点修改
某个包裹的数据录错了,比如下标 3 的实际重量应该是 8,而不是 3。这时你不能只改一张表——因为包含下标 3 的所有上级账本([0,7]、[0,3]、[2,3]、[3,3])都会受到影响,必须一路重新汇总。
单点修改的步骤很直观:从根节点出发,根据目标下标 pos 与 mid 的比较,选择向左子树或右子树递归;当到达叶子 L == R == pos 时,更新叶子的值。然后在回溯的路上,用左右子树的当前值重新计算每个祖先节点的 tree[p]。
修改路径的长度就是树的高度 log2 n。所以单点修改的时间复杂度也是 O(log n)——只需要更新从根到叶一条链上的节点,其他节点完全不受影响。这比数组的 O(1) 修改慢一些,但换来的是区间查询也能达到 O(log n)。
这就像街道站改了一笔单据后,立刻向市中转站、大区分仓逐级上报修正,但其他片区的账本完全不需要动。由于每次修改都精确沿着一条路径走,数据的正确性得以快速恢复。
- 定位叶子从根开始,根据
pos <= mid判断进入左孩子还是右孩子,直到L == R == pos。 - 更新叶子值到达叶子后,将数组的该位置更新为新值。
- 回溯汇总从叶子沿原路返回,每到一个祖先就重新计算左右孩子之和。
懒传播机制
如果接到一个大订单:某大区所有站点的包裹数统一加上 5。你如果挨个站点改账本,那就太慢了。聪明的调度员会在分仓的账本上贴一张便签:“本片区每件 +5”——等以后真正需要向下派送或查询时,再根据便签补算。这就是懒传播(lazy propagation)的思路。
线段树中的懒标记(lazy tag)正是这样一张便签:当区间修改操作“完全覆盖”某个节点时,我们不深入修改它的所有子孙节点,只修改这个节点自身的值,并在 lazy[p] 上记下“这里欠着一笔更新”。等到后续查询或修改必须访问它的孩子时,再执行 push_down(下推),把 lazy[p] 分发给左右孩子。
下推的过程也很简单:如果节点 p 有懒标记 lazy[p],就把它累加到两个孩子身上,同时更新两个孩子的 tree 值,最后清空 p 的懒标记。这样,每次访问子节点前,信息都是最新的。
回到快递比喻,懒传播像极了仓库管理中的“批量登记”:先在大仓账本上贴一张便签,等小仓库真的来调货时再逐单结算。这样一来,区间修改的时间也被压缩到 O(log n)——因为每层只需改一个节点的账本和便签,不必触碰整棵子树。
v 时,节点 p 管辖的区间有 r-l+1 个元素,所以总值增加 v × 长度,同时把增量打在懒标记上。- 计算区间长度节点
p覆盖的区间长度为len = r - l + 1,整体加v相当于总值增加v × len。 - 更新自身汇总直接把增量合并进节点
tree[p],让它立即反映正确的区间和。 - 记录懒标记在
lazy[p]中累计这一个“未下发的增量”,留待需要时再传给子孙。
优化策略
如果全公司的仓库编号是稀疏的,比如只有 0、100、5000、100000 这几家站点,盖 10 万个仓库的账本显然浪费。聪明的做法之一是坐标离散化:把所有可能出现的坐标排序、去重、重新编号成 1、2、3、4,然后用这份连续编号去建树。这样原本 10 万 × 的空间被压缩到 4 个有效位置。
另一种策略叫动态开点:不预先分配 4n 的数组,而是“用多少、开多少”。线段树一开始只有一个根节点,每次递归要访问左孩子或右孩子时,先检查该节点是否已存在,若不存在就现场申请新节点编号。对海量范围、少量操作的场景(例如 n=10^9 但只操作 10^5 次),动态开点能省下海量空间。
这两个优化思路可以叠加使用:先离散化缩小范围,再用动态开点避免构建整棵完整的树;此外,如果线段树是按“值域”来建,就演变成了权值线段树——每个叶子代表一个数值,统计该数值出现的次数,可以用来求区间第 k 大、频次统计等。权值线段树是许多进阶算法(如主席树)的基石。
这就像仓库公司不按门牌号逐号建账,而是把实际有业务的几个仓址重新编号:10008号仓 → 1号仓、99999号仓 → 2号仓。账本只覆盖真实存在的位置,而后端调度算法完全不用改——这样既省空间,又保持了查询的高效。
- 排序去重将所有可能出现的坐标收集起来,排序并去掉重复值,得到有序序列。
- 分配排名每个原始坐标在有序序列中的位置就是它的新编号。
- 用排名建树用压缩后的连续区间
[1, m]作为线段树的管辖范围。
高级扩展
两家快递公司合并时,若两套分仓体系里都有“华东大区”,最好的办法是把对应的大区账本合并成一本:逐级递归地合并左子树、右子树,叶子节点的数据相加。这在算法里就叫线段树合并。合并的复杂度与两棵树重叠的节点数成正比,通常很快。
另一种更高阶的需求是“查看历史快照”:比如系统需要回到 10 分钟前的状态,看看当时的区间和是多少。持久化线段树(俗称主席树)的做法很巧妙——每次修改不复制整棵树,而是只复制从根到被改叶子的一条链,其他子树继续复用旧版本。这样每次操作只产生 O(log n) 个新节点,却能保留所有历史版本。
线段树合并在处理“多棵小线段树合并成一棵大树”的场景(如并查集式树上操作)中非常常用;而主席树则广泛用于静态区间第 k 大、离线查询等问题。这两个扩展都建立在线段树“结构灵活、可以动态分配”的基础上。
在快递比喻里,线段树合并就是把两家公司的分仓一一“对账合成”,持久化则是每次改动只抄写一条新的账目链,老账本原封不动地存作历史档案。掌握了这些“升级版”技巧后,线段树就不只是一个查询工具,而是一个可以组合、可以回溯的完整“账本系统”。
- 空节点处理若有一棵子树为空,直接返回另一棵,避免无效递归。
- 合并左孩子递归合并
u和v的左孩子,得到新的左孩子。 - 合并右孩子递归合并
u和v的右孩子,得到新的右孩子。 - 重新汇总合并完成后,用两个孩子的值重新计算节点
u的统计值。
应用与对比
日常调度中,我们会有很多类似的问题:想查某个片区包裹最大单件重量(区间最值)、想查某几条街道的总重量(区间求和)、想给某一片区统一打上“优惠区”标记并反复查询(区间染色)。这三类问题恰好对应线段树的三个经典应用:RMQ(区间最值)、区间求和、区间染色/赋值。
其中区间求和还有一个漂亮的思维:任何区间和都可以用前缀和相减得到。树状数组正是利用这个思想,用更轻量的结构完成单点修改 + 前缀查询,但它天然不支持“区间修改 + 区间查询”这类复杂操作。线段树通过懒传播可以做到任意区间修改、任意区间查询,表达力更强;代价是实现更复杂、常数更大。
| 维度 | 线段树 | 树状数组 |
|------|--------|----------|
| 代码难度 | 较高,需递归/懒标记 | 很低,十几行 |
| 区间求和 | 任意区间修改 + 查询 | 单点修改 + 前缀查询 |
| 区间最值 | 支持 | 较难(不支持区间修改) |
| 区间染色 | 支持 | 不支持 |
| 时间复杂度 | 均为 O(log n) | 均为 O(log n) |
在物流场景中,无论你是想找最重包裹、算总重量,还是标记优惠片区,一套分层账本系统都能应对;树状数组则像一张“精简的速查表”,适合单纯的前缀汇总。根据业务复杂度选择合适的工具,才是最高效的调度策略。
[l,r] 的和转化为两个前缀和之差,这是树状数组的基础,也是理解线段树区间查询的一把钥匙。- 定义前缀和
pref(i)表示从下标 0 到 i 的累计和。 - 相减得区间和用右端点的前缀和减去左端点前一位的前缀和,即得
[l,r]的和。 - 线段树上的类比和前缀和一样,线段树节点保存的区间汇总值允许我们“拼装”出任意子区间,而不是逐元素访问。
AI 应用场景
实时特征工程中的滑窗聚合
阶段:特征工程 / 离线与在线特征计算
解决的问题:推荐系统需要实时统计用户最近 N 次行为的总时长、最大消费金额、或者某个滑动窗口内的点击率峰值。传统做法是每次查询都遍历窗口内的全部记录,数据量大时延迟高。线段树可以在保障 O(log n) 查询速度的同时支持动态更新(新行为不断写入),使滑窗聚合既快又准。
具体案例:某短视频推荐平台需要统计“用户过去 1 小时浏览视频的最长单次观看时长”和“总观看时长”,用于实时兴趣建模。将时间轴划分成桶,用线段树维护每个桶的总观看时长与最大值,当新观看记录到达时做单点更新,刷新推荐特征时做区间查询。实测在每秒万级写入下,P99 延迟低于 5ms。
时序指标异常检测中的区间极值监控
阶段:模型服务监控 / 数据可观测性
解决的问题:AI 模型部署后,需要实时监测推理延迟、GPU 利用率等指标。传统轮询全量扫描无法快速定位“某一时间段内的最高延迟是否超阈值”。利用线段树的区间最值查询,可以在毫秒级返回任意时间窗口的峰值,辅助快速告警。
具体案例:某 MLOps 平台对线上推理服务的延迟数据按秒分桶后构建线段树,每次新延迟上报只需一次单点更新;当值班人员查询“最近 15 分钟最大延迟是否超过 800ms”时,一次区间查询即可秒级返回。若检测到异常区间,还可进一步用区间求和快速计算超时请求总量,辅助根因分析。
Python 演示
class SegmentTree: """支持区间加法和区间求和的线段树(懒传播)""" def __init__(self, data): self.n = len(data) self.tree = [0] * (4 * self.n) self.lazy = [0] * (4 * self.n) self._build(data, 1, 0, self.n - 1) def _build(self, data, node, left, right): if left == right: self.tree[node] = data[left] return mid = (left + right) // 2 self._build(data, node * 2, left, mid) self._build(data, node * 2 + 1, mid + 1, right) self.tree[node] = self.tree[node * 2] + self.tree[node * 2 + 1] def _apply(self, node, left, right, val): self.tree[node] += val * (right - left + 1) self.lazy[node] += val def _push_down(self, node, left, right): if self.lazy[node] == 0 or left == right: return mid = (left + right) // 2 left_child = node * 2 right_child = node * 2 + 1 self._apply(left_child, left, mid, self.lazy[node]) self._apply(right_child, mid + 1, right, self.lazy[node]) self.lazy[node] = 0 def update_range(self, ql, qr, val): self._update_range(1, 0, self.n - 1, ql, qr, val) def _update_range(self, node, left, right, ql, qr, val): if ql <= left and right <= qr: self._apply(node, left, right, val) return self._push_down(node, left, right) mid = (left + right) // 2 if ql <= mid: self._update_range(node * 2, left, mid, ql, qr, val) if qr > mid: self._update_range(node * 2 + 1, mid + 1, right, ql, qr, val) self.tree[node] = self.tree[node * 2] + self.tree[node * 2 + 1] def query_range(self, ql, qr): return self._query_range(1, 0, self.n - 1, ql, qr) def _query_range(self, node, left, right, ql, qr): if ql <= left and right <= qr: return self.tree[node] self._push_down(node, left, right) mid = (left + right) // 2 result = 0 if ql <= mid: result += self._query_range(node * 2, left, mid, ql, qr) if qr > mid: result += self._query_range(node * 2 + 1, mid + 1, right, ql, qr) return result if __name__ == "__main__": data = [2, 1, 5, 3, 4, 7, 6, 9] st = SegmentTree(data) print("初始数组:", data) print("区间 [2,5] 的和:", st.query_range(2, 5)) st.update_range(1, 4, 3) print("给区间 [1,4] 整体加 3 后") print("区间 [2,5] 的和:", st.query_range(2, 5)) st.update_range(0, 7, 1) print("给整体加 1 后") print("区间 [0,6] 的和:", st.query_range(0, 6))
__main__ 输出验证结果。self.tree = [0] * (4 * self.n)用 4 倍数组空间存储线段树,防止越界。self._build(data, 1, 0, self.n - 1)从根节点下标 1 开始递归建树,管辖区间[0, n-1]。if left == right:判断到达叶子节点,直接把原始数组元素写入。self.tree[node] = self.tree[node * 2] + self.tree[node * 2 + 1]回溯时用左右孩子求和得到父节点汇总值。if ql <= left and right <= qr:完全覆盖判断,直接给整个区间的和加上val × 长度并打懒标记。self._apply(left_child, left, mid, self.lazy[node])下推时把父节点的懒标记累加到左孩子。self._push_down(node, left, right)查询或修改要进入子树前,先把尚未下发的懒标记推给两个孩子。result += self._query_range(node * 2, left, mid, ql, qr)递归查询左半部分并累加结果。st.update_range(1, 4, 3)一行代码完成区间整体加 3,验证懒传播确实生效。
常见误区
误区我看线段树长得像二叉树,就觉得必须要用 `left`、`right` 指针建树,不用指针就写不出来。
正解线段树大多数情况下是用数组实现的:因为每次二分区间,节点下标具备规律的倍数关系 2p 和 2p+1,天然适合连续数组存储。指针实现不仅浪费空间,还会让代码复杂化、缓存不友好。只有动态开点等特殊场景才会借助“类似指针”的节点编号方式。
为何常见:初学二叉树时,链表结构深入人心,容易把所有“树”都默认成指针实现;忽略了线段树“顺序存储”的数学规律。
误区给区间 `[1, 100000]` 整体加 5,我就递归到每个叶子节点,于是复杂度变成 O(n),难怪超时。
正解这正是懒标记要解决的问题——区间修改时若当前节点被查询区间完全覆盖,只需修改该节点的统计值和 lazy 标记,不必深入更新子孙。只有当后续操作真正需要访问子节点时,才把标记下推。这样区间修改的复杂度从 O(n) 降为 O(log n)。
为何常见:刚写完单点修改的人容易“惯性递归”,认为所有修改都必须立刻在叶子上落实,忽略了“延迟处理”的威力。
误区既然是“万能”树,那我就可以用它求区间众数、求区间最长递增子序列,甚至求区间随机采样。
正解线段树能高效处理的是“满足结合律”的信息,例如和、最大值、最小值、gcd、按位或等。如果一个区间统计无法通过合并两个子区间快速推导(如众数、严格 LIS 长度),普通线段树也无能为力,需要更复杂的分块或特殊技巧。
为何常见:这些误导来自于过度推广线段树功能,其实每个数据结构都有它的“能力边界”。
误区树状数组代码短、速度快,那我以后都用树状数组,没必要学线段树。
正解树状数组擅长“单点修改 + 前缀查询”,也能用两个 BIT 实现区间修改 + 区间求和,但无法自然支持区间最值、区间染色、动态开点等更复杂的操作。线段树在功能覆盖面上严格优于树状数组;树状数组则在常数和代码量上更优。二者是互补关系,不是替代关系。
为何常见:因为很多算法题只用树状数组就能过,容易让人低估线段树的扩展能力,直到遇到区间最值 + 区间修改才追悔莫及。
面试题
Q1. 线段树中,节点覆盖区间 [l, r],其左孩子覆盖的区间是?
- A.
[l, (l+r)/2-1] - B.
[l, (l+r)/2]← 正确答案 - C.
[l, r/2] - D.
[l+1, r/2]
答案:B。设 mid = floor((l+r)/2),左孩子覆盖 [l, mid],右孩子覆盖 [mid+1, r]。选项 B 正确,其他选项要么少了 l 到 mid 的连续性,要么错误地空出了中间位置。
Q2. 为什么线段树通常开 4 倍数组空间而不是 2 倍?
- A.为了对齐内存,提高访问速度
- B.因为递归划分会产生非严格满二叉树,4n 才足够容纳所有可能的节点← 正确答案
- C.方便多存一层备用节点
- D.因为 2n 只会越界,实际需要 8n
答案:B。n 个元素的线段树是一棵不完全的满二叉树,最坏情况下最后一层还会产生额外节点,2n 在边界情况下会越界(如 n 不是 2 的幂时)。开 4n 可以稳妥覆盖所有节点,代码简单且无越界风险。
Q3. 懒标记(lazy tag)的核心思想是?
- A.每次区间修改都立即更新所有叶子节点
- B.区间完全覆盖时只更新当前节点并记录标记,需要访问孩子时再下推← 正确答案
- C.只在根节点上做修改,不关心子节点
- D.用定时器延迟所有修改操作
答案:B。懒传播的核心是“延迟更新”。区间修改完全覆盖某个节点时,直接更新该节点并打上 lazy 标记,不递归进入子孙;等后续查询/修改必须访问孩子时再用 push_down 下推。这样可以确保区间修改的复杂度为 O(log n)。
Q4. 以下哪些操作可以用线段树在 O(log n) 内完成?
- A.区间最大值查询← 正确答案
- B.区间求和← 正确答案
- C.区间染色(覆盖颜色后查询某段颜色种类数)← 正确答案
- D.求任意区间的最长严格递增子序列长度
答案:ABC。区间最值(A)、区间求和(B)、区间染色(C)都满足结合律,可以用线段树加速。而区间最长严格递增子序列(LIS)无法仅通过合并两个子区间快速推导,普通线段树不能直接高效求解。
Q5. 一次单点更新,线段树上最多需要更新多少个节点?
- A.所有叶子节点
- B.从根到叶子的一条路径,共 O(log n) 个节点← 正确答案
- C.只有目标叶子本身
- D.整棵树的一半节点
答案:B。单点修改从根沿着路径走到叶子,再回溯更新路径上的每个祖先。树高为 log2 n,因此需要更新的节点数就是 O(log n),其他分支的节点值不受影响。
Q6. 关于线段树与树状数组,下列说法正确的是?
- A.树状数组代码更短,常数更小← 正确答案
- B.线段树支持区间修改 + 区间查询,适应性更广← 正确答案
- C.在单点修改 + 前缀求和场景下,两者时间复杂度都是 O(log n)← 正确答案
- D.树状数组可以完全替代线段树
答案:ABC。树状数组实现简单、速度快,更擅长单点修改与前缀查询;线段树功能更强大,支持区间修改/区间最值/染色等。两者在经典场景下都是 O(log n),但树状数组无法完全替代线段树,故 D 错。
Q7. 给定值域范围 [1, 10^9],但实际只操作 10^5 个离散点,选哪种线段树策略最合适?
- A.直接开 4 × 10^9 的数组
- B.使用普通数组模拟
- C.使用坐标离散化或动态开点← 正确答案
- D.用链表遍历所有值
答案:C。值域巨大而操作点稀疏,直接建满 4n 数组必然内存溢出。离散化把稀疏坐标压缩到连续排名,动态开点则按需分配节点,两者都能把空间降到 O(m),m 为实际操作点数。
Q8. 区间查询中,当节点区间被查询区间完全覆盖时,正确做法是?
- A.继续递归到叶子
- B.直接返回该节点的值← 正确答案
- C.先修改该节点的值再返回
- D.跳过该节点继续找兄弟节点
答案:B。完全覆盖是查询的“终止条件”。因为该节点的值已经正确汇总了其管辖区间所有元素的信息,直接返回即可,无需再向下递归。这也是线段树查询高效的关键。