题目描述

✅ 447. 回旋镖的数量

image-20260929100711124

image-20260929100711249

题意分析

从互不相同的平面点中,统计三个不同点组成的有序三元组 (i, j, k),要求 i 到 j 的距离等于 i 到 k 的距离。i 是中心,交换 j、k 后是另一个回旋镖。

解法:按距离分组计数

核心思路

[!blue]

先固定中心 i,问题就变成:从距离中心相同的一组点中,选出两个不同点并安排顺序。若这组有 m 个点,第一位有 m 种选择,第二位有 m - 1 种选择,贡献为 m * (m - 1)。

用哈希表按距离平方分组,平方距离相等就等价于距离相等,无需开方。代码在扫描时直接计数:当前点到来之前,同距离已有 c 个点,它与每个旧点都能按两种顺序配对,所以新增 2 * c,然后才将频次加一。每对点恰好在后扫描到的那个点到来时计入,不会重复或遗漏。

每换一个中心就创建新的频次表。不同中心对应三元组的第一项不同,计数可以直接相加;扫描其他点时跳过中心自身,保证三个点互不相同。

解题步骤

  1. 枚举每个点 i 作为中心,创建空频次表 cnt。
  2. 遍历所有 j != i 的点,计算 d2 = dx * dx + dy * dy。
  3. 取出旧频次 c = cnt[d2],将答案增加 2 * c,再令 cnt[d2] = c + 1。
  4. 所有中心处理完后返回总答案。点不足三个时,自然不会形成有效配对。

代码实现

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int n = points.length;
        int answer = 0;

        for (int i = 0; i < n; i++) {
            Map<Integer, Integer> cnt = new HashMap<>();

            for (int j = 0; j < n; j++) {
                if (j == i) {
                    continue;
                }

                int dx = points[i][0] - points[j][0];
                int dy = points[i][1] - points[j][1];
                int d2 = dx * dx + dy * dy;
                // 使用加入前的频次,每个同距离旧点贡献两种有序配对。
                int same = cnt.getOrDefault(d2, 0);

                answer += 2 * same;
                cnt.put(d2, same + 1);
            }
        }

        return answer;
    }
}
func numberOfBoomerangs(points [][]int) int {
    n := len(points)
    answer := 0
    for i := 0; i < n; i++ {
        cnt := make(map[int]int)
        for j := 0; j < n; j++ {
            if j == i {
                continue
            }
            dx := points[i][0] - points[j][0]
            dy := points[i][1] - points[j][1]
            d2 := dx*dx + dy*dy
            // 使用加入前的频次,每个同距离旧点贡献两种有序配对。
            answer += 2 * cnt[d2]
            cnt[d2]++
        }
    }
    return answer
}

复杂度分析

  • 时间复杂度:期望 $O(n^2)$,其中 n 为点数;每个中心扫描全部其他点,每次进行常数次哈希操作。
  • 空间复杂度:$O(n)$,只保存当前中心的距离计数。

关键点总结

[!green]

  • 有序对需计入两个排列方向。
  • 频次表随中心重建,不能跨中心累计。
  • 贡献使用加入当前点之前的频次。

易错点总结

[!yellow]

  • 使用组合数只计一次顺序:答案少一半。
  • 只枚举下标比中心大的点:漏掉中心左侧下标的合法邻点。
  • 更新频次后用新频次计算贡献:把当前点与自身配对。
  • 不同中心共用未清空的表:混淆不同距离分布。

相似题目

题目 难度 关联与区别
149. 直线上最多的点数 困难 同样固定锚点再把其余点按特征分组,原题按方向分组,本题按距离平方分组。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/32493751
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!