LeetCode之路:447. Number of Boomerangs

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012814856/article/details/74315644

一、引言

这道题还算比较有意思。

先看看题目吧:

Given n points in the plane that are all pairwise distinct, a “boomerang” is a tuple of points (i, j, k) such that the distance between i and j equals the distance between i and k (the order of the tuple matters).

Find the number of boomerangs. You may assume that n will be at most 500 and coordinates of points are all in the range [-10000, 10000] (inclusive).

Example:
Input:
[[0,0],[1,0],[2,0]]

Output:
2

Explanation:
The two boomerangs are [[1,0],[0,0],[2,0]] and [[1,0],[2,0],[0,0]]

照惯例简单翻译下:

给定 n 个成对的平面上的点,一个 “boomerang” 就是一组元组 (i, j, k),其中 i 到 j 的距离等于 i 到 k 的距离(这组点的顺序决定了元组是否相同)

请找到 “boomerang” 的个数。你可以假定 n 不会超过 500,并且坐标系不会超过 [-10000, 10000] 的范围。

根据题意,我们了解到了我们要在 n 个平面上的点中寻找这么一个元组:

(i, j, k) ,其中 i 到 j 的距离等于 i 到 k 的距离

我们只需要在 n 个点中寻找这个元组能够拼凑出来的个数即可。

其实这道题思路还是比较简单的:

  1. 首先,遍历所有的点

  2. 然后,以一个点为基准点计算与其他所有点的距离,将距离记录在一个缓存映射中

  3. 最后,将每个点中的距离缓存映射中的出现次数大于 2 的记录进行排列计算(出现次数中取 2 的排列),最后叠加起来即可

思考到这一步,剩下的就是如何编写代码了。

二、核心:出现次数中取 2 的排列

根据引言中的思路,编写代码如下:

// my solution 1 , runtime = 329 ms
class Solution1 {
public:
    int numberOfBoomerangs(vector<pair<int, int>>& points) {
        int count = 0;
        for (auto cur_pt : points) {
            map<double, int> distance;
            for (auto otr_pt : points)
                if (cur_pt != otr_pt)
                    distance[pow(otr_pt.first - cur_pt.first, 2) + pow(otr_pt.second - cur_pt.second, 2)]++;
            for (auto dis : distance)
                if (dis.second > 1)
                    count += dis.second * (dis.second - 1);
        }
        return count;
    }
};

可以看到这段代码中:

  1. 对所有点进行了遍历,以达到取到每个点的功能

  2. std::map 实现了计算的举例结果的出现次数的记录。计算两点间的距离使用了 std::pow 函数

  3. 最后的一个 for 循环,将每一个点的距离缓存进行遍历,将其中出现次数大于了 2 次的进行组合计算,比如距离计算为 1 的点出现了 3 次,那么 3 中取 2 的排列(之所以是排列,因为顺序影响元组),也就是 3 * 2 = 6 次

这个方法比较简单,其实还是有优化空间的。

比如,我们没必要除去本身的点的计算(因为始终为 0 ),此处可以简化代码行数。

再比如这里的计算距离的公式,可以使用 std:hypot:

distance[hypot(otr_pt.first - cur_pt.first, otr_pt.second - cur_pt.second)]++;

计算两个点之间的距离,使用 std::hypot 函数最合适不过了。

想要详细了解这个函数的同学可以点击这里:

C++在线参考手册之std::hypot

三、殊途同归:最高票答案难以理解的算法

做到了这里,尽管已经 AC 了,但是还是要看看最高票答案的玄妙:

// perfect solution 
class Solution2 {
public:
    int numberOfBoomerangs(vector<pair<int, int>>& points) {
        int booms = 0;
        for (auto &p : points) {
            unordered_map<double, int> ctr(points.size());
            for (auto &q : points)
                booms += 2 * ctr[hypot(p.first - q.first, p.second - q.second)]++;
        }
        return booms;
    }
};

这个方法比我的方法少了一次遍历,也就是少了那一次对于映射的遍历,也就是说最高票答案将缓存 map 的计算移到了第二次循环中去了。

让我们仔细看看,他是怎么计算的:

  1. 当获取到了基准点到每个点的距离的时候,我的方法是计算排列(出现次数中取 2 的排列),而他则是 2 * 上一次的出现值

  2. 让我们想想,这样可以计算结果吗?答案当然是可以的。让我们想想啊:

    距离为 1 的点第 1 次出现:
    booms += 2 * 0; // 只加了 0 次
    距离为 1 的点第 2 次出现:
    booms += 2 * 1; // 加了 2 次
    距离为 1 的点第 3 次出现:
    booms += 2 * 2; // 加了 4 次

    也就是说,我们可以这么想,当前的基准点到两个点的排列即为我们的 “boomerang” 点,那么每增加一个可以凑成同样距离的 “boomerang” 点之后,我们其实就要增加 2 * 当前同距离点的个数 个 “boomerang” 元组可能(也就是该点到同距离的各个点之后,再换个位置,所以 * 2)

经过了我的分析,相信你也理解了最高票答案的算法。

这里最高票答案除了使用了特别的算法之外,还使用了 unordered_map 来提高运行效率,非常可取。

四、总结

这道题还是比较有趣的,做出来也没花多少时间。

利用公司中午的午休时间写的博客,依然喜爱着 LeetCode :)

To be Stronger!

展开阅读全文

没有更多推荐了,返回首页