题目描述:
有
n
个城市,按从0
到n-1
编号。给你一个边数组edges
,其中edges[i] = [fromi, toi, weighti]
代表fromi
和toi
两个城市之间的双向加权边,距离阈值是一个整数distanceThreshold
。返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为
distanceThreshold
的城市。如果有多个这样的城市,则返回编号最大的城市。注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。
可以很明显的看出这是一道 Floyd 的裸题,当我们通过 Floyd 算法求出两两点之间的最短路径后,就可以遍历每个节点,并统计出该节点到多少个其他节点的距离在阈值范围内,最终判断是否要更新结果。
Floyd 算法是一个基于贪心 + 动态规划的经典多源最短路径算法,可以正确处理负权图(无负环)。
以上图为例,我们要找 1
到 2
的最短路径,有两种可能性:
- 最短路径中包括
7
; - 最短路径中不包括
7
;
对于情况一,既然最短路径中包括 7
,说明最短路径一定是** 1
到 7
的最短路径加上 7
到 2
的最短路径**;对于情况二,可以直接跳过 7
,那么就变成了最短路径是否包含 6
的子问题。
设 表示从 i 到 j 的最短路长度,并且这条最短路的中间节点编号都 ,那么根据上面的讨论可以得出递归表达式:。
从 2
到 1
的最短路可以分解出从 2
到 7
的最短路(中间节点 ),从 2
到 3
的最短路也可以分解出从 2
到 7
的最短路(中间节点 )。也就是说,都会递归到 。可以发现递归会包含大量重复计算,因此可以用记忆化搜索进行优化。
public class Solution {
public int findTheCity(int n, int[][] edges, int distanceThreshold) {
int[][] graph = new int[n][n];
for (int[] row : graph) {
Arrays.fill(row, Integer.MAX_VALUE / 2); // 防止加法溢出
}
for (int[] e : edges) {
int x = e[0], y = e[1], w = e[2];
graph[x][y] = graph[y][x] = w;
}
int[][][] memo = new int[n][n][n];
int ans = 0;
int minCnt = n;
for (int i = 0; i < n; i++) {
int cnt = 0;
for (int j = 0; j < n; j++) {
if (j != i && dfs(n - 1, i, j, memo, graph) <= distanceThreshold) {
cnt++;
}
}
if (cnt <= minCnt) { // 相等时取最大的 i
minCnt = cnt;
ans = i;
}
}
return ans;
}
private int dfs(int k, int i, int j, int[][][] memo, int[][] graph) {
if (k < 0) { // 递归边界
return graph[i][j];
}
if (memo[k][i][j] != 0) { // 之前计算过
return memo[k][i][j];
}
return memo[k][i][j] = Math.min(dfs(k - 1, i, j, memo, graph),
dfs(k - 1, i, k, memo, graph) + dfs(k - 1, k, j, memo, graph));
}
}
将上面的代码翻译成递推,并进行空间优化后得到经典的 Floyd 算法代码。
class Solution {
public int findTheCity(int n, int[][] edges, int distanceThreshold) {
int[][] graph = new int[n][n];
for(int[] row : graph) {
Arrays.fill(row, Integer.MAX_VALUE / 2);
}
for(int[] e : edges) {
int u = e[0], v = e[1], w = e[2];
graph[u][v] = graph[v][u] = w;
}
int[][] dis = graph;
for(int k = 0; k < n; k++) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
dis[i][j] = Math.min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
int ans = 0, minCnt = n;
for(int i = 0; i < n; i++) {
int cnt = 0;
for(int j = 0; j < n; j++) {
if(i != j && dis[i][j] <= distanceThreshold) {
cnt++;
}
}
if(cnt <= minCnt) {
minCnt = cnt;
ans = i;
}
}
return ans;
}
}