[H枚举] lc149. 直线上最多的点数(枚举+细节优化+数学)

Posted Ypuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[H枚举] lc149. 直线上最多的点数(枚举+细节优化+数学)相关的知识,希望对你有一定的参考价值。

1. 题目来源

链接:149. 直线上最多的点数

大佬题解:直线一般式,C++ STL 应用

2. 题目解析

枚举+算法优化。

暴力做法:

  • 枚举直线,两点确定一条直线, O ( n 2 ) O(n^2) O(n2)
  • 针对每个直线,确定直线上有多少点,直接套直线方程即可 O ( n ) O(n) O(n)
  • 总的时间是 O ( n 3 ) O(n^3) O(n3)

优化做法:

  • 不先将所有直线枚举出来,而只枚举直线上的一个点,称为中心点
  • 那么经过这个点的直线是 360° 任意多条的。
  • 然后再枚举其他各点。
  • 当枚举另一个点时有两种情况:
    • 它与中心点构成一条新的直线,则保存该直线的斜率即可,用于判断之后的其它点是否在该直线上。
    • 它与中心点构成的直线已经出现过,即斜率相等,则在该直线上加上这个点的数量即可。
  • 任意一点都可能成为中心点,通过枚举其它各点与中心点的连线,我们可以得到所有的直线。每次即可求得当前中心点所在直线的最大点数数量。
  • 先枚举中心点,再枚举其余各点。故这个的复杂度是 O ( n 2 ) O(n^2) O(n2) 的。

细节:

  • 针对直线的表示,在此直接使用斜截式表示即可,注意小数精度问题。
  • 斜截式求斜率时,分母为零情况注意,即垂线情况,需要单独用变量纪录这条直线。
  • 如果某点与中心点重叠,则该点会落到所有直线上,需要单独使用变量记录和中心点重叠的点数。
  • 则最终统计哪条直线点数最多时,需要统计比较正常直线上的点数、垂线上的点数,最后再加上和中心点重叠的点数。
  • 本题精度要求较高,需要使用 long double

关于使用两点式和斜截式:

  • 两点式不需要讨论分母为 0 的这种垂线情况,将直线可以直接化为一般式,以下是我一些总结:
    在这里插入图片描述
  • 尤其注意两点式的变形情况,这个方程就不涉及分母为 0 的情况了。简单整理可以得到直线的一般式: A x + B y + C = 0 等 价 于 x ( y 2 − y 1 ) + y ( x 1 − x 2 ) + x 1 ( y 1 − y 2 ) + y 1 ( x 2 − x 1 ) = 0 Ax+By+C=0 等价于 x(y_2-y_1)+y(x_1-x_2)+x_1(y_1-y_2)+y_1(x_2-x_1)=0 Ax+By+C=0x(y2y1)+y(x1x2)+x1(y1y2)+y1(x2x1)=0
    其可以与平面上的直线一一对应,但是不同的 A , B , C A,B,C A,B,C 可能存在倍数关系,即可能出现直线共线的情况。所以需要对 A , B , C A,B,C A,B,C 约掉其最大公约数,保证直线唯一。

极其优质:

当使用 unordered_map 时,需要自己传入 hash 函数。


时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)


class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
        typedef long double LD;

        int res = 0;
        for (auto p : points) {
            int s1 = 0, s2 = 0;     // s1 和中心点相同,s2 直线
            unordered_map<LD, int> cnt;
            for (auto q : points) {
                if (p == q) s1 ++ ;
                else if (q[0] == p[0]) s2 ++ ;
                else {
                    LD k = (LD)(q[1] - p[1]) / (q[0] - p[0]);
                    cnt[k] ++ ;
                }
            }
            int c = s2;
            for (auto [k, v] : cnt) c = max(v, c);
            res = max(res, c + s1);
        }

        return res;
    }
};

大佬的写法:

arraytuple 均可。类似于这种类型、pair<int,int> 等均需要自己传入哈希函数。

// 计算三元组的哈希值
struct v3_hash {
  size_t operator()(const array<int, 3> &x) const {
    return x[0] ^ x[1] ^ ((size_t)x[2] << 32);
  }
};

class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
      // Ax + By + C = 0, (A, B, C)
      // A(x1 - x2) + B(y1 - y2) = 0
      // (A, B, C) = (y2 - y1, x1 - x2, x2 * y1 - x1 * y2)
      int best = 0;
      for (int i = 0; i < points.size(); ++i) {
        // 记录同一条直线的出现次数
        unordered_map<array<int, 3>, int, v3_hash> cnts;
        const int x1 = points[i][0];
        const int y1 = points[i][1];
        for (int j = i + 1; j < points.size(); ++j) {
          const int x2 = points[j][0];
          const int y2 = points[j][1];
          // 计算表示一条直线的三元组
          array<int, 3> v3 = {y2 - y1, x1 - x2, x2 * y1 - x1 * y2};
          bool first_non_zero = true;  // 判断是否遇到第一个非零元素
          bool minus = false;  // 记录三元组第一个非零元素是否为负数
          int g = 0;  // 变量g记录最大公约数
          for (int k = 0; k < 3; ++k) {
            if (v3[k] != 0) {
              if (first_non_zero) {
                first_non_zero = false;
                minus = v3[k] < 0;
                g = abs(v3[k]);
              } else {
                g = __gcd(g, abs(v3[k]));
              }
            }
          }
          // 对三元组进行约分
          if (g != 0) {
            if (minus) g = -g;
            for (int k = 0; k < 3; ++k) {
              if (v3[k] != 0) v3[k] /= g;
            }
          }
          best = max(best, ++cnts[v3]);
        }
      }
      // 直线上最多的点数 = 同一条直线出现的最大次数 + 1
      return best + 1;
    }
};

简单优化,在此题目保证了各点互不相同,则 gcd() 一定不为 0,不会出现除 0 错误,则对 A、B、C 求最大公约数除了就行了,但是会不会差一个符号呢?

其实可能是会的,__gcd 是忽略符号的,即 __gcd(-2, -6)=-2,则正号依旧是正号,负号依旧是负号,若 + - + = 0- + - = 0,那么 gcd 后两者符号刚好交换,等价于两条直线了,但在本题貌似没有出现这个情况。


不知道能否严格证明该情况不会出现。 或是这次对了只是侥幸?

struct v3_hash {
  size_t operator()(const array<int, 3> &x) const {
    return x[0] ^ x[1] ^ ((size_t)x[2] << 32);
  }
};

class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
      int best = 0;
      for (int i = 0; i < points.size(); ++i) {
        unordered_map<array<int, 3>, int, v3_hash> cnts;
        const int x1 = points[i][0];
        const int y1 = points[i][1];
        for (int j = i + 1; j < points.size(); ++j) {
          const int x2 = points[j][0];
          const int y2 = points[j][1];
          // 计算表示一条直线的三元组
          int A = y2 - y1, B = x1 - x2, C = x2 * y1 - x1 * y2;
          int t = __gcd(__gcd(A, B), __gcd(B, C));
          A /= t, B /= t, C /= t;				// 直接化简
          array<int, 3> v3 = {A, B, C};

          best = max(best, ++cnts[v3]);
        }
      }
      return best + 1;
    }
};

以上是关于[H枚举] lc149. 直线上最多的点数(枚举+细节优化+数学)的主要内容,如果未能解决你的问题,请参考以下文章

题目地址(149. 直线上最多的点数)

力扣python149. 直线上最多的点数

LeetCode第149题—直线上最多的点数—Python实现

LeetCode第149题—直线上最多的点数—Python实现

leetcode 149. 直线上最多的点数

leetcode 149. 直线上最多的点数