SWE · Second Phone Coding

Snowflake 子树和改写新树:后序计算与有限线程并行

电面公开把每个节点改成原树子树和,追问大量子树但线程有限;先用后序遍历证明正确,再分析依赖与工作池。

题目重点:电面公开把每个节点改成原树子树和,追问大量子树但线程有限;先用后序遍历证明正确,再分析依赖与工作池。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练版输入一棵有根树:values[node] 是原始整数值,children[node] 给有序子节点;输出同样节点集合的 node→subtreeSum,新映射不修改 values。
  • 所有节点都可从 root 到达,节点只有一个父亲;环、共享子节点或未知子节点报错。题目未公开树形数据结构和并行 API。
  • 并行追问只讨论正确调度与规模取舍,不宣称有限线程一定比顺序更快。

解题路线

  1. 后序计算 S(node)=originalValue(node)+ΣS(child);必须先拿到每个子节点的结果,再写父节点,且读的始终是原始值,避免原地修改的混淆。
  2. 示例用显式 enter/exit 栈做后序,处理完成节点时读取 child 结果;校验输入是单根树并输出新 map。
  3. 并行时把就绪叶节点放入有界工作队列,每个父节点维护未完成子节点计数;最后一个 child 完成时把父节点变为可执行。工作线程数量为 P,避免每节点新建线程。
  4. 若每个节点工作只是一次加法,线程调度开销通常超过收益;只对大子树或复杂节点计算做任务切分,需保证同一父节点聚合的原子性。
  5. 随机树与独立递归后序参考做 10,000 组对照,包括负值、深链和多分支。

手动走一遍

根 A 值 2,孩子 B 值 3、C 值 -1,B 有孩子 D 值 4。新树对应 D=4、B=7、C=-1、A=8。并行时 B 与 C 可并行计算,但 A 必须等 B/C 都完成;若 P=2,可让一个线程处理 B 子树,另一个处理 C。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Rewrite each node's value as its original subtree sum, without mutation."""


def subtree_sums(root: str, values: dict[str, int], children: dict[str, list[str]]) -> dict[str, int]:
    if root not in values:
        raise ValueError("missing root")
    if any(child not in values for group in children.values() for child in group):
        raise ValueError("unknown child")
    result = {}
    visiting = set()
    visited = set()
    stack = [(root, False)]
    while stack:
        node, exiting = stack.pop()
        if exiting:
            result[node] = values[node] + sum(result[child] for child in children.get(node, []))
            visiting.remove(node)
            visited.add(node)
            continue
        if node in visiting or node in visited:
            raise ValueError("not a tree")
        visiting.add(node)
        stack.append((node, True))
        stack.extend((child, False) for child in reversed(children.get(node, [])))
    if visited != set(values):
        raise ValueError("disconnected nodes")
    return result

复杂度

N 个节点、E=N−1 边,顺序后序 O(N+E) 时间和 O(N+E) 输出/工作空间。并行的理想下界是 max(总工作/P,最长根叶依赖链),还要加队列与同步开销;单链树无法靠兄弟并行加速。

易错点与追问

容易失分的边界

  • 父节点在所有 child 结果完成前求和,得到部分结果。
  • 原地覆盖节点值后又把它当原始值重复累加。
  • 对每个子树开新线程,在‘无数多个子树’时耗尽线程和内存。
  • worker 阻塞等待自己提交的 child future,固定大小线程池可能死锁。
  • 宣称树后序一定能线性加速,忽略关键路径和调度成本。

继续深挖

  • 每个节点还有昂贵 I/O 时,工作池和背压怎样调?
  • 若树数据跨机器分片,如何处理父子归属与重试幂等?
  • 若需要增量更新一个叶值,哪些祖先和受影响?
✓已复制微信号!