题目描述

✅ 1334. 阈值距离内邻居最少的城市

image-20260928224245644

image-20260928224245645

image-20260928224245646

image-20260928224245647

题意分析

城市之间是双向带权道路。对每个城市,统计最短路径长度不超过 distanceThreshold 的其他城市数,返回数量最少的城市;数量相同时取编号最大的。距离等于阈值也算,自身不计入邻居。

解法:Floyd 全源最短路

核心思路

[!blue]

需要知道每一对城市的最短距离,而城市数最多只有 100,可以用 Floyd 算法一次求出所有点对。dist[start][end] 表示当前允许的中转城市范围内,两城之间的最短距离;初始不允许任何中转,只保留直连边、自身距离 0 和不可达标记 inf。

每轮新增一个可用的中转城市 middle。最短路要么不经过它,沿用旧距离;要么经过它,拆成 start 到 middle、middle 到 end 两段,候选长度为两段距离之和。边权为正,最短路无需重复经过同一城市,这两种情况便覆盖了全部可能,取较小值即可。

middle 必须放在最外层:处理这一轮时,两段距离才都已包含此前所有中转城市的结果。又因为 dist[middle][middle] = 0,当前轮不会进一步改变通向 middle 或从它出发的距离,所以可以直接在二维矩阵中原地更新。任意一段不可达时不能组成路径,应先跳过。

最后按编号从小到大统计邻居。bestCount 保存目前最少的邻居数,当前数量小于或等于它时都更新答案;相等也覆盖,才能让较大的编号留下。没有任何邻居的城市计数为 0,同样正常参与比较。

解题步骤

  • 初始化自身零、其他不可达,写入无向边。
  • 逐中间点松弛所有点对,任一段不可达则跳过。
  • 统计阈值内的其他城市,按少量与大编号规则选择。

代码实现

class Solution {
    public int findTheCity(int n, int[][] edges, int distanceThreshold) {

        int inf = 1_000_000_000;
        int[][] dist = new int[n][n];

        for (int start = 0; start < n; start++) {
            for (int end = 0; end < n; end++) {
                if (start == end) {
                    dist[start][end] = 0;
                } else {
                    dist[start][end] = inf;
                }
            }
        }

        for (int[] edge : edges) {
            int from = edge[0];
            int to = edge[1];
            int weight = edge[2];

            if (weight < dist[from][to]) {
                dist[from][to] = weight;
                dist[to][from] = weight;
            }
        }

        // 中间城市是动态规划阶段,必须先于点对枚举。
        for (int middle = 0; middle < n; middle++) {
            for (int start = 0; start < n; start++) {
                for (int end = 0; end < n; end++) {

                    // 任一段不可达,就不能组成经过当前中间城市的路径。
                    if (dist[start][middle] == inf || dist[middle][end] == inf) {
                        continue;
                    }

                    dist[start][end] =
                            Math.min(dist[start][end], dist[start][middle] + dist[middle][end]);
                }
            }
        }

        int bestCity = -1;

        int bestCount = n + 1;

        for (int city = 0; city < n; city++) {
            int count = 0;

            for (int other = 0; other < n; other++) {
                if (city != other && dist[city][other] <= distanceThreshold) {
                    count++;
                }
            }

            // 升序扫描时允许相等覆盖,实现并列取较大编号。
            if (count <= bestCount) {
                bestCount = count;
                bestCity = city;
            }
        }

        return bestCity;
    }
}
func findTheCity(n int, edges [][]int, distanceThreshold int) int {

    const inf = 1_000_000_000
    dist := make([][]int, n)

    for start := 0; start < n; start++ {
        dist[start] = make([]int, n)
        for end := 0; end < n; end++ {
            if start == end {
                dist[start][end] = 0
            } else {
                dist[start][end] = inf
            }
        }
    }

    for _, edge := range edges {
        from, to, weight := edge[0], edge[1], edge[2]

        if weight < dist[from][to] {
            dist[from][to] = weight
            dist[to][from] = weight
        }
    }

    // 中间城市是动态规划阶段,必须先于点对枚举。
    for middle := 0; middle < n; middle++ {
        for start := 0; start < n; start++ {
            for end := 0; end < n; end++ {

                // 任一段不可达,就不能组成经过当前中间城市的路径。
                if dist[start][middle] == inf || dist[middle][end] == inf {
                    continue
                }

                if dist[start][middle]+dist[middle][end] < dist[start][end] {
                    dist[start][end] = dist[start][middle] + dist[middle][end]
                }
            }
        }
    }

    bestCity := -1

    bestCount := n + 1
    for city := 0; city < n; city++ {
        count := 0
        for other := 0; other < n; other++ {
            if city != other && dist[city][other] <= distanceThreshold {
                count++
            }
        }

        // 升序扫描时允许相等覆盖,实现并列取较大编号。
        if count <= bestCount {
            bestCount = count
            bestCity = city
        }
    }

    return bestCity
}

复杂度分析

  • 时间复杂度:$O(n^3)$,三重循环求最短路;初始化和最终统计均为 $O(n^2)$。
  • 空间复杂度:$O(n^2)$,完整距离矩阵。

关键点总结

[!green]

  • 中间点维度表达允许使用的中转集合。
  • 等于阈值也可达,并列数量时用后来编号覆盖。
  • 题面正权路径的最短路可去掉重复城市,最多含 $n-1$ 条边,inf = 10^9 足以与真实距离区分。

易错点总结

[!yellow]

  • 只比较直连边,会漏掉更便宜的绕路。
  • 升序遍历却仅在严格更少时更新,会保留较小编号。
  • 把带权距离当成边数,会改变可达范围。
  • 将中转点放进内层只遍历一次,可能在所需子路径尚未求出时就错过更新。
  • 没有排除自身,会把对角线上的距离 0 也统计进去。

相似题目

题目 难度 关联与区别
743. 网络延迟时间 中等 原题从一个源求最短距离,本题需要对每个城市统计阈值内的可达城市数。
1462. 课程表 IV 中等 同样可用全源传递更新,原题维护布尔可达性,本题维护带权最短距离并按阈值统计。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/12953406
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!