SWE Intern · 75-minute OA Coding

Microsoft Intern OA 树直径所有端点:三次 BFS 找外围节点

从任意点找一对直径端点,再以两端距离识别所有可能成为某条直径端点的节点;区别只返回任意一对。

题目重点:从任意点找一对直径端点,再以两端距离识别所有可能成为某条直径端点的节点;区别只返回任意一对。

题意与假设

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

  • 输入是无权、无向、连通树,节点编号训练版为 0..n−1。
  • 返回所有可能作为某条最长路径端点的节点,按编号升序;若面试官只要任意一对,第二次 BFS 的两端即可。
  • 单节点树的唯一节点既是起点也是终点,返回 [0];空图返回 [],题目未明确边界。

解题路线

  1. 从任意点 0 BFS 得最远点 a;树的最远点可作为某条直径的端点。
  2. 从 a BFS 得距离 da 和最远点 b,D=da[b] 为树直径长度;再从 b BFS 得 db。
  3. 树的性质:任意顶点 v 的离心率(到全树最远顶点距离)等于 max(da[v],db[v]),这里 a、b 是一对直径端点。因此 v 是某条直径的端点当且仅当该值等于 D。
  4. 扫全部节点返回满足条件者。用迭代 BFS 避免深链树递归栈问题;输入若有环或断开,直径的树性质不可直接套。

手动走一遍

星形树中心 0、叶 1/2/3:直径长 2,任意两叶之间都是直径。若只做两次 BFS 可能只得到 1 与 2,漏掉 3;两端距离判据会返回 [1,2,3]。路径树 0−1−2−3 只返回 [0,3]。

Python 参考实现

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

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

from collections import deque


def all_diameter_endpoints(n: int, edges: list[tuple[int, int]]) -> list[int]:
    """All peripheral vertices in a 0-based undirected tree."""
    if n == 0:
        if edges:
            raise ValueError("edges without nodes")
        return []
    if len(edges) != n - 1:
        raise ValueError("not a tree")
    graph = [[] for _ in range(n)]
    for u, v in edges:
        if not 0 <= u < n or not 0 <= v < n or u == v:
            raise ValueError("invalid edge")
        graph[u].append(v)
        graph[v].append(u)

    def bfs(start: int) -> list[int]:
        distance = [-1] * n
        distance[start] = 0
        queue = deque([start])
        while queue:
            node = queue.popleft()
            for neighbor in graph[node]:
                if distance[neighbor] == -1:
                    distance[neighbor] = distance[node] + 1
                    queue.append(neighbor)
        if -1 in distance:
            raise ValueError("disconnected input")
        return distance

    first = bfs(0)
    a = max(range(n), key=lambda i: first[i])
    distance_a = bfs(a)
    b = max(range(n), key=lambda i: distance_a[i])
    distance_b = bfs(b)
    diameter = distance_a[b]
    return [i for i in range(n) if max(distance_a[i], distance_b[i]) == diameter]


if __name__ == "__main__":
    assert all_diameter_endpoints(4, [(0, 1), (0, 2), (0, 3)]) == [1, 2, 3]

复杂度

每次 BFS 处理 n 个节点和 n−1 条边,三次仍 O(n) 时间;邻接表与距离数组 O(n) 空间。

易错点与追问

容易失分的边界

  • 把一次 DFS 最深节点集合当所有直径端点,根的选择会改变结果。
  • 只返回找到的一对 a,b,若题意是所有外围节点则不完整。
  • 把树直径长度按节点数和边数混用;代码的 D 是边数。
  • 未确认‘所有最两端节点’的返回口径就把训练版解释成唯一原题。

继续深挖

  • 若只要求直径长度和任意一对端点,最少几次 BFS?
  • 给有权树且权重非负时,如何替换 BFS?
  • 若输入是一般图,三次 BFS 的外围节点判据还成立吗?
✓已复制微信号!