代表性面试主题

如何实现 Fenwick Tree,并支持动态前缀和与按权重选项?

编程题困难
OfferCC 编辑团队发布 更新

题干

请实现 Fenwick Tree,支持单点加法、前缀和与区间和;如果每个位置代表一个非负权重,再支持按给定排名找到对应位置。说明 lowbit、1-based 下标、建树复杂度和输入不满足单调累计和时的限制。

1. 题目

有一个长度为 n 的动态频率表,位置 i 的值会被频繁增加,系统需要查询前缀和、区间和,并按累计权重找到第 k 个单位落在哪个位置。请实现 Fenwick Tree(Binary Indexed Tree),要求单点更新和前缀查询为 O(log n),并解释它与普通前缀数组、线段树的取舍。

2. 约束与澄清

  • 内部数组使用 1-based 下标;公开接口可以接受 0-based 位置,但必须统一转换一次。
  • 更新可以是增量,也可以先计算差值再写入;要明确是否允许负值。
  • k 的排名从 1 开始,只有所有权重非负且总和至少为 k 时才定义按权重选择。
  • 先讨论单线程结构;并发更新需要额外锁或分片,不能假设普通整数写入自动形成一致快照。

3. 核心思路

树状数组的第 i 项保存一个连续区间的部分和,区间长度由 lowbit(i) = i & -i 决定。前缀查询不断减去 lowbit,单点更新不断加上 lowbit,因此每次只访问 O(log n) 个数组位置。区间和用两个前缀相减;若初始数组已知,可以用线性传播把每个值累加到其父索引,建树为 O(n)。

4. 参考实现

text
class Fenwick:
  init(values):
    tree = [0] * (len(values) + 1)
    for i from 1 to len(values):
      tree[i] += values[i - 1]
      parent = i + lowbit(i)
      if parent < len(tree):
        tree[parent] += tree[i]

  add(index0, delta):
    i = index0 + 1
    while i < len(tree):
      tree[i] += delta
      i += lowbit(i)

  prefixSum(index0Exclusive):
    total = 0
    i = index0Exclusive
    while i > 0:
      total += tree[i]
      i -= lowbit(i)
    return total

  rangeSum(left0, right0Exclusive):
    return prefixSum(right0Exclusive) - prefixSum(left0)

按权重选择时,从最高的二进制步长开始试探:若跳到候选索引后的累计和仍小于 k,就接受该步并减少 k;最后得到的下标加一就是第 k 个单位所在的位置。该操作依赖累计和单调,权重出现负数时不能直接使用。

5. 复杂度与取舍

Fenwick Tree 使用 O(n) 数组,单点增量、前缀和和按权重选择均为 O(log n);线性建树为 O(n)。它比线段树更紧凑、常数更小,但只能自然表达可逆的前缀聚合,难以直接支持区间最小值、复杂区间更新或保留完整分段信息。若所有数据只读,普通前缀数组查询是 O(1);若需要频繁更新,Fenwick Tree 才体现价值。

6. 验证与观测

  • 对随机数组比较每次 add、prefixSum 和 rangeSum 与朴素数组结果,覆盖空数组、单元素和最大下标。
  • 测试全为零、权重很大、累计和恰好等于 k、k 超出总和以及非法下标。
  • 交叉验证线性建树与逐点 add 建树的内部数组和查询结果。
  • 对按权重选择生成非负随机权重,逐个 k 检查返回位置的前缀和边界;单独拒绝负权重输入。

7. 常见误区

  • 混用 0-based 和 1-based 下标,导致位置 0 不更新或最后一个位置越界。
  • 把 i & -i 当作取负号技巧,却没有解释它表示最低位的二进制块。
  • 用 Fenwick Tree 处理带负值的按权重选择,忽略累计和不再单调。
  • 更新值直接覆盖树节点,而不是沿更新路径累加 delta。

8. 面试评分点

能解释 lowbit 与区间覆盖

应说明每个树节点保存哪段连续区间,以及查询和更新为何沿 lowbit 路径跳转。

能写出无边界错误的实现

应统一 1-based 内部下标,处理空数组、非法位置和右开区间,并保证更新不会访问数组末端之外。

能推导复杂度与建树方式

应给出查询、更新、选择的 O(log n) 和线性建树的 O(n),并比较前缀数组与线段树的适用边界。

能识别按权重选择的前提

应指出权重必须非负、累计和必须单调,并用边界测试验证 k 恰好命中、超界和大数情况。

公开来源

同类题目

资源

产品对比

每份对比均基于官方资料,并标注复核日期。

查看完整对比

相关面试工具

用 Screenshot 处理算法题

截图题目后,按顺序看约束、解法、代码、边界条件和复杂度。

查看工具