news 2026/9/13 17:56:13

Floyd 多源最短路——O(n³) 也能优雅:三重循环里藏着动态规划(LeetCode 1334 + 1462)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Floyd 多源最短路——O(n³) 也能优雅:三重循环里藏着动态规划(LeetCode 1334 + 1462)

引子:Dijkstra 回答不了一个问题

Dijkstra 是单源算法:给我一个起点,我告诉你到所有点的最短距离。但很多问题问的是**"所有点两两之间"——比如 LeetCode 1334:"哪些城市在阈值距离内可达的邻居最少?"你要对每一个城市**做一次源点。

暴力方案是跑 n 次 Dijkstra(O(n·m·logn)),但 n 小、图稠密时,有一个更简单、更"优雅得让人着迷"的答案:Floyd-Warshall——三行核心循环,O(n³),把所有点对的最短路一次算完。而且它还是"传递闭包"(LeetCode 1462 先修关系)的天然载体:把min换成or,把+换成and,同一框架两题通吃。

本文拆解三重循环里藏着的动态规划,并给出两题的完整代码与验证。


一、为什么需要"多源"最短路

1.1 单源 vs 多源

  • 单源:一个起点 → 到所有点(Dijkstra / SPFA)

  • 多源:所有点两两之间(Floyd / n×Dijkstra)[3]

1.2 两个高频场景

  1. 阈值计数(1334):对每个城市统计"阈值内可达邻居数"——本质是"这个城市是中心还是边缘"

  2. 闭包查询(1462):课程 A 是不是课程 B 的间接先修——本质是可达性而非距离

这两个问题都要求"全对全"的信息,天然属于 Floyd[1][2]。


二、Floyd 核心:以 k 为中转的动态规划

2.1 状态定义与转移

d[i][j]= 从 i 到 j 的最短距离。Floyd 的关键洞察:任意一条最短路,要么不经过某个点 k,要么经过 k——于是:

d[i][j] = min( d[i][j], d[i][k] + d[k][j] )

外层循环 k 从 0 到 n-1,含义是:逐步允许"前 k 个点"作为中转站。当 k 遍历完,所有点对的最短路就确定了[3]。

INF = float('inf') for k in range(n): # 允许使用 0..k 作为中转点 for i in range(n): for j in range(n): if d[i][k] + d[k][j] < d[i][j]: d[i][j] = d[i][k] + d[k][j]

2.2 为什么 k 在最外层

如果 k 在内层,d[i][k]可能还没"允许 k 之前的点中转",递推就不完整。k 必须在外层——这是 Floyd 正确性的第一道纪律[3][4]。


三、三重循环的正确性:滚动数组的秘密

3.1 三维 DP 到二维滚动

完整的状态应该带维度 k:

d[k][i][j] = min( d[k-1][i][j], # 不用 k 中转 d[k-1][i][k] + d[k-1][k][j] ) # 用 k 中转

但代码里我们只开二维,直接原地更新。为什么不会出错?关键在于:第 k 轮更新d[i][j]时读到的d[i][k]d[k][j]恰好还是 k-1 轮的旧值——因为"用 k 作为中转点"不会让d[i][k]d[k][j]变得更小(以 k 为端点,中转 k 是原地绕圈,在非负环假设下无效)[3]。

一句话:滚动数组之所以安全,是因为"中转点 k 不会优化以 k 为端点的路径"——这是 Floyd 最容易被追问的考点[3][4]。


四、LeetCode 1334:阈值距离内邻居最少的城市

4.1 题意与思路

有 n 个城市(0..n-1)和无向带权边,给定 distanceThreshold。对每个城市 i,统计满足d[i][j] <= distanceThreshold的城市 j 的数量;返回数量最少的城市,若有并列,返回编号最大的那个。

思路三步走:

  1. Floyd 求全源最短路

  2. 对每个城市逐行计数

  3. 并列取最大编号 →倒序遍历(遇到更小计数才更新)

4.2 完整代码

def findTheCity(n: int, edges: list[list[int]], distanceThreshold: int) -> int: INF = float('inf') d = [[INF] * n for _ in range(n)] for i in range(n): d[i][i] = 0 for u, v, w in edges: d[u][v] = min(d[u][v], w) d[v][u] = min(d[v][u], w) for k in range(n): for i in range(n): for j in range(n): if d[i][k] + d[k][j] < d[i][j]: d[i][j] = d[i][k] + d[k][j] best, city = n + 1, -1 for i in range(n - 1, -1, -1): # 倒序保证并列取最大编号 cnt = sum(1 for j in range(n) if d[i][j] <= distanceThreshold) if cnt < best: best, city = cnt, i return city
  • 复杂度:O(n³) 时间 / O(n²) 空间;n≤100 直接过,n≤500 亦可接受[1][4]


五、LeetCode 1462:课程表 IV(传递闭包)

5.1 布尔版 Floyd

1334 求距离,1462 只问可达。把 Floyd 的两个算子换掉:

d[i][j] = min(d[i][j], d[i][k] + d[k][j]) # 距离版 reach[i][j] |= reach[i][k] and reach[k][j] # 布尔版

这就是传递闭包(Warshall 算法)——如果 i 能到达 k,k 能到达 j,那么 i 能到达 j[2][4]。

5.2 完整代码

def checkIfPrerequisite(numCourses: int, prerequisites: list[list[int]], queries: list[list[int]]) -> list[bool]: reach = [[False] * numCourses for _ in range(numCourses)] for a, b in prerequisites: reach[a][b] = True for k in range(numCourses): for i in range(numCourses): for j in range(numCourses): if reach[i][k] and reach[k][j]: reach[i][j] = True return [reach[a][b] for a, b in queries]

样例验证(本地实测)

输入

输出

结果

n=2, [[1,0]], 查询[[0,1],[1,0]]

[False, True]

n=5, 链式先修 0→1→2→3→4

[True,False,True,False]

n=3, [[1,2],[1,0],[2,0]]

[True,True]


六、选型与总结

6.1 选型表

场景

推荐

复杂度

理由

n≤500、稠密图、多源

Floyd

O(n³)/O(n²)

实现最简单,常数小

n 大、稀疏图、多源

n×Dijkstra(堆)

O(n·m·logn)

稀疏图明显更快

单源、正权

Dijkstra(堆)

O(m·logn)

单源最优

负权无负环

Floyd / Bellman-Ford

O(n³) / O(n·m)

Floyd 亦可判负环

只要可达性

Warshall 闭包

O(n³)/O(n²)

布尔版 Floyd

6.2 三个关键结论

  1. Floyd 是"以中转点扩张"的 DP:k 的外层顺序、滚动数组的安全性、无负环假设,三件事一体理解——这是面试追问的黄金考点[3];

  2. 一框架两变体:1334 用距离版,1462 用布尔版,算子一换、问题域就换——理解 Floyd 的抽象结构比背代码重要得多[1][2];

  3. 选型看 n 与稀疏度:O(n³) 不是洪水猛兽,n≤500 时它常比 n 次堆 Dijkstra 更简单可靠;先想规模,再选算法[4]。

学习路线:Dijkstra(单源,第28篇)→Floyd(多源,本篇)→ 传递闭包(1462)→ Johnson 算法(稀疏图多源,SPFA+重标号)→ 最小生成树(第34篇)对照"连通 vs 最短路"两种图优化目标。把 1334 + 1462 亲手跑一遍,你会记住:三重循环不是暴力,是递推。


参考资料

信源编号对应02-情报.md信源清单。

  • [1] LeetCode 1334 官方题面(阈值距离内邻居最少的城市)

  • [2] LeetCode 1462 官方题面(课程表 IV)

  • [3] CLRS《算法导论》第 25 章:所有结点对的最短路径(Floyd-Warshall 证明)

  • [4] OI Wiki:Floyd 算法(实现细节、选型、负环判定)

验证说明:1334 两组样例、1462 三组样例均于 2026-09-12 本地运行通过(Python 3.12)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 17:53:14

PDFPatcher PDF 工具箱新手指南

PDFPatcher PDF 工具箱新手指南 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址: https://gitcode.com/GitHub_Trending/pd/PDF…

作者头像 李华
网站建设 2026/9/13 17:52:55

解决Node.js连接MySQL 8.0认证协议不兼容问题

1. 问题现象与背景分析最近在本地开发环境搭建Node.js后端服务时&#xff0c;遇到了一个典型的数据库连接问题。当我尝试用mysql2包连接新安装的MySQL 8.0数据库时&#xff0c;控制台抛出了如下错误&#xff1a;ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentic…

作者头像 李华
网站建设 2026/9/13 17:52:28

15个Verilog文件造出一颗GPU:tiny-gpu极简并行架构拆解

15个Verilog文件造出一颗GPU&#xff1a;tiny-gpu极简并行架构拆解 【免费下载链接】tiny-gpu A minimal GPU design in Verilog to learn how GPUs work from the ground up 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny-gpu 如果你只能给一颗GPU写11条指令…

作者头像 李华