题目重点:同一图问题先在所有边权为 1 时用 BFS,追问边权不再为 1 时用非负权 Dijkstra,解释负权边界。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练图无向,可有多条边和零权边,带权版所有权重非负;单位权版每边权重恰 1。
- 顶点编号 0..n−1,返回按编号排列的最短距离;不可达返回 None。
- 若改成有向图,只去掉反向邻接边即可;若有负权边,Dijkstra 不成立,应按题目约束选 Bellman-Ford 等算法。
解题路线
- 单位权时,从 0 做 BFS,第一次到达任一顶点的层数就是最少边数,也就是最短距离;队列保证逐层扩展。
- 改为非负带权后,最少边数不等于最小权重和。用最小堆按当前候选距离取节点,对每条边做松弛,跳过已过期堆项。
- Dijkstra 正确性依赖非负权:堆中最小距离节点被取出时,任何绕道尚未处理节点都不可能再降低它;零权仍可用。
- 题目只说‘非 1’没有明示非负,面试时必须问负权/零权、有向性和不可达口径;不能直接把 Dijkstra 当无条件 follow-up 答案。
手动走一遍
边 0–1 权 10、0–2 权 1、2–1 权 1。单位权图从 0 到 1 直接一条边是最短;带权后绕 2 的代价 2 小于直接代价 10。若 3 是孤点,训练版输出 None。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Distances from vertex zero in nonnegative weighted or unit-edge graphs."""
from collections import deque
import heapq
def distances_from_zero(n: int, edges: list[tuple[int, int, int]], weighted: bool = False) -> list[int | None]:
if n <= 0:
raise ValueError("at least vertex zero required")
graph = [[] for _ in range(n)]
for source, target, weight in edges:
if not 0 <= source < n or not 0 <= target < n:
raise ValueError("vertex outside graph")
if weight < 0 or (not weighted and weight != 1):
raise ValueError("nonnegative weighted edges or unit unweighted edges required")
graph[source].append((target, weight))
graph[target].append((source, weight)) # Training contract: undirected.
distance: list[int | None] = [None] * n
distance[0] = 0
if not weighted:
queue = deque([0])
while queue:
node = queue.popleft()
for neighbor, _ in graph[node]:
if distance[neighbor] is None:
distance[neighbor] = distance[node] + 1
queue.append(neighbor)
return distance
heap = [(0, 0)]
while heap:
current, node = heapq.heappop(heap)
if distance[node] != current:
continue
for neighbor, weight in graph[node]:
candidate = current + weight
if distance[neighbor] is None or candidate < distance[neighbor]:
distance[neighbor] = candidate
heapq.heappush(heap, (candidate, neighbor))
return distance
复杂度
n 点 m 边:单位权 BFS O(n+m) 时间与空间;允许重边且堆里保留旧候选的本实现上界 O((n+m) log(n+m)) 时间、O(n+m) 存储。
易错点与追问
容易失分的边界
- 边权变为非 1 后仍只比较 BFS 层数。
- 有负权边却使用 Dijkstra 并声称正确。
- 题目未说明无向图,却不声明训练版的反向边假设。
- 不可达点留为巨大 sentinel 后当正常距离输出。
继续深挖
- 若边权只为 0 或 1,如何用双端队列做 0-1 BFS?
- 若有负权但无负环,Bellman-Ford 的成本是多少?
- 若频繁查询不同起点,是否值得预处理全源最短路?
