【leetcode 447. 回旋镖的数量】审慎思考与推倒重来
题目描述
给定平面上 **
n**对 互不相同 的点points,其中points[i] = [xi, yi]。回旋镖 是由点(i, j, k)表示的元组 ,其中i和j之间的距离和i和k之间的欧式距离相等(需要考虑元组的顺序)。返回平面上所有回旋镖的数量。
原始思路
根据题目描述,所谓回旋镖就是对于一个点i,存在另外两个点到该点的距离相等。
以下图为例,点(0,0)到(0,2)、(2,0)的距离是相通的,这就是一个回旋镖。同样的,点(2,2)与(0,2)、(2,0)也构成一个回旋镖。

大概理解题目的含义后,最直接的想法就是遍历,通过三重遍历分辨判断是否为回旋镖,最后的统计数目就是需要的结果。代码实现如下:
class Solution {
private:
    // 判断点i与点j、点k是否构成回旋镖
    // ps:这里省略了开方运算
    bool isBoomeranges(vector<int>& i, vector<int>& j, vector<int>& k) {
        return ((j[1] - i[1]) * (j[1] - i[1])) + ((j[0] - i[0]) * (j[0] - i[0])) == ((k[1] - i[1]) * (k[1] - i[1])) + ((k[0] - i[0]) * (k[0] - i[0]));
    }
public:
    int numberOfBoomerangs(vector<vector<int>>& points) {
        int res = 0;
        int pointNum = points.size();
        for(int i = 0;i < pointNum;++i) {
            for(int j = 0;j < pointNum;++j) {
                for(int k = 0;k < pointNum;++k) {
                    if(i == j || i == k || j == k) {
                        // 同一个点不算
                        continue;
                    }
                    if(isBoomeranges(points[i], points[j], points[k])) {
                        ++res;
                    }
                }
            }
        }
        return res;
    }
};
三重循环,不出意外地超时了……

试图挽回,空间换时间
三重循环每次都要计算距离,肯定是做了很多重复运算的,或许可以用空间换时间,
尝试代码如下:
class Solution {
public:
    int numberOfBoomerangs(vector<vector<int>>& points) {
        int res = 0;
        int pointNum = points.size();
        vector<vector<long>> dis(pointNum, vector<long>(pointNum, 0)); // 记录点与点之间的“距离”
        for(int i = 0;i < pointNum;++i) {
            for(int j = i + 1;j < pointNum;++j) {
                dis[j][i] = dis[i][j] = ((points[j][1] - points[i][1]) * (points[j][1] - points[i][1])) + ((points[j][0] - points[i][0]) * (points[j][0] - points[i][0]));
            }
        }
        for(int i = 0;i < pointNum;++i) {
            for(int j = 0;j < pointNum;++j) {
                for(int k = 0;k < pointNum;++k) {
                    if(i == j || i == k || j == k) {
                        continue;
                    }
                    if(dis[i][j] == dis[i][k]) {
                        ++res;
                    }
                }
            }
        }
        return res;
    }
};
结果从通过25个测试用例提升到通过28个。改善了,但又没完全改善。

到这里,意识到应该是解决方案本身的时间复杂度 O ( n 3 ) O(n^3) O(n3)就太高了。
回归本源,方法为王
虽然上面的思路简单易懂,但也把我带入“歧途”,没有深入审视题目中的含义。
所以遍历思路走到尽头后,不得不重新审视题目。
三重循环中有很多计算是重复的,还是以开头的例子做讲解,对于点(1,1),它需要分别判断:
- 与(0,0)、(0,2)是否构成回旋镖
- 与(0,0)、(2,2)是否构成回旋镖
- 与(0,0)、(2,0)是否构成回旋镖
- 与(0,2)、(0,0)是否构成回旋镖
- 与(0,2)、(2,2)是否构成回旋镖
- 与(0,2)、(2,0)是否构成回旋镖
- 与(2,2)、(0,0)是否构成回旋镖
- 与(2,2)、(0,2)是否构成回旋镖
- 与(2,2)、(2,0)是否构成回旋镖
- 与(2,0)、(0,0)是否构成回旋镖
- 与(2,0)、(0,2)是否构成回旋镖
- 与(2,0)、(2,2)是否构成回旋镖
在这个过程中,(1,1)和(0,0)、(0,2)、(2,2)、(2,0)的距离,分别被算了6遍。
但实际上,(1,1)到这四个点的距离都是相同的,任取两个点都能和(1,1)构成回旋镖,再加上顺序敏感的题目要求(既 
     
      
       
       
         [ 
        
       
         ( 
        
       
         1 
        
       
         , 
        
       
         1 
        
       
         ) 
        
       
         , 
        
       
         ( 
        
       
         0 
        
       
         , 
        
       
         0 
        
       
         ) 
        
       
         , 
        
       
         ( 
        
       
         0 
        
       
         , 
        
       
         2 
        
       
         ) 
        
       
         ] 
        
       
      
        [(1,1),(0,0),(0,2)] 
       
      
    [(1,1),(0,0),(0,2)]与 
     
      
       
       
         [ 
        
       
         ( 
        
       
         1 
        
       
         , 
        
       
         1 
        
       
         ) 
        
       
         , 
        
       
         ( 
        
       
         0 
        
       
         , 
        
       
         2 
        
       
         ) 
        
       
         , 
        
       
         ( 
        
       
         0 
        
       
         , 
        
       
         0 
        
       
         ) 
        
       
         ] 
        
       
      
        [(1,1),(0,2),(0,0)] 
       
      
    [(1,1),(0,2),(0,0)]是两个回旋镖),利用排列数计算就能替代多余计算。

所以,对于某个点i,只需统计其他点与i之间的距离,然后利用排列数就可以计算出回旋镖的数目。
 比如上面的例子中,距离为 
     
      
       
        
        
          2 
         
        
       
      
        \sqrt{2} 
       
      
    2?的点对有4个,那么 
     
      
       
        
        
          A 
         
        
          4 
         
        
          2 
         
        
       
         = 
        
       
         4 
        
       
         × 
        
       
         3 
        
       
         = 
        
       
         12 
        
       
      
        A^2_4 = 4 \times 3 = 12 
       
      
    A42?=4×3=12。
算法步骤:
- 遍历所有点
- 对于某个点i,统计与其他点的距离长度
- 对每个距离长度的数目,计算排列数
- 所有排列数之和即为答案
class Solution {
private:
    int distance(const vector<int>& p1, const vector<int>& p2) {
        // 计算两点之间的“距离”
        return (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]);
    }
public:
    int numberOfBoomerangs(vector<vector<int>>& points) {
        int res = 0;
        int pointNum = points.size();
        for(int i = 0;i < pointNum;++i) {
            // 遍历所有点
            unordered_map<int, int> disNumMp;
            for(int j = 0;j < pointNum;++j) {
                // 统计该点与其他之间距离长度的数目
                // 只有该点自己到自己的长度为0,所以0对应的数目一定为0,不影响最终结果计算,无需剔除
                ++disNumMp[distance(points[i], points[j])];
            }
            for(auto it = disNumMp.begin();it != disNumMp.end();++it) {
                // 计算排列数
                res += (it->second * (it->second - 1));
            }
        }
        return res;
    }
};
小感悟
有些时候,人容易对一开始选择的路径产生依赖,不推倒重来就难以跳脱。
审慎的思考和选择或许可以避免弯路,但更多时候还是要走到南墙才能意识到选择的对错。
所以,要有审慎选择的过程,也要有推倒重来的勇气!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!