直线上最多的点数

给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。

示例 1:

输入:points = [[1,1],[2,2],[3,3]]
输出:3

示例 2:

输入:points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出:4

思路及解法

我们可以考虑枚举所有的点,假设直线经过该点时,该直线所能经过的最多的点数。

假设我们当前枚举到点i,如果直线同时经过另外两个不同的点jk,那么可以发现点i和点j所连直线的斜率恰等于点i和点k所连直线的斜率。

于是我们可以统计其他所有点与点i所连直线的斜率,出现次数最多的斜率即为经过点数最多的直线的斜率,其经过的点数为该斜率出现的次数加一(点i自身也要被统计)。

如何记录斜率

需要注意的是,浮点数类型可能因为精度不够而无法足够精确地表示每一个斜率,因此我们需要换一种方法来记录斜率。

一般情况下,斜率可以表示为\textit{slope} = \dfrac{\Delta y}{\Delta x}的形式,因此我们可以用分子和分母组成的二元组来代表斜率。但注意到存在形如\dfrac{1}{2}=\dfrac{2}{4}这样两个二元组不同,但实际上两分数的值相同的情况,所以我们需要将分数\dfrac{\Delta y}{\Delta x}化简为最简分数的形式。

将分子和分母同时除以二者绝对值的最大公约数,可得二元组\Big(\dfrac{\Delta x}{\gcd(|\Delta x|,|\Delta y|)},\dfrac{\Delta y}{\gcd(|\Delta x|,|\Delta y|)}\Big)

\textit{mx}=\dfrac{\Delta x}{\gcd(|\Delta x|,|\Delta y|)}\textit{my}=\dfrac{\Delta y}{\gcd(|\Delta x|,|\Delta y|)},则上述化简后的二元组为(\textit{mx},\textit{my})

此外,因为分子分母可能存在负数,为了防止出现形如\dfrac{-1}{2}=\dfrac{1}{-2}的情况,我们还需要规定分子为非负整数,如果\textit{my}为负数,我们将二元组中两个数同时取相反数即可。

特别地,考虑到\textit{mx}\textit{my}两数其中有一个为0的情况(因为题目中不存在重复的点,因此不存在两数均为0的情况),此时两数不存在数学意义上的最大公约数,因此我们直接特判这两种情况。当\textit{mx}为 000 时,我们令\textit{my}=1;当\textit{my}0时,我们令\textit{mx}=1即可。

经过上述操作之后,即可得到最终的二元组(\textit{mx},\textit{my})。在本题中,因为点的横纵坐标取值范围均为[-10^4, 10^4],所以斜率\textit{slope} = \dfrac{\textit{my}}{\textit{mx}}中,\textit{mx}落在区间[- 2 \times 10^4, 2 \times 10^4]内,\textit{my}落在区间[0, 2 \times 10^4]内。注意到32 位整数的范围远超这两个区间,因此我们可以用单个32位整型变量来表示这两个整数。具体地,我们令\textit{val} = \textit{my} + (2 \times 10^4 + 1) \times \textit{mx}即可。

优化

最后我们再加四个小优化:

在点的总数量小于等于2的情况下,我们总可以用一条直线将所有点串联,此时我们直接返回点的总数量即可;
当我们枚举到点i时,我们只需要考虑编号大于i的点到点i的斜率,因为如果直线同时经过编号小于点i的点j,那么当我们枚举到j时就已经考虑过该直线了;
当我们找到一条直线经过了图中超过半数的点时,我们即可以确定该直线即为经过最多点的直线;
当我们枚举到点i(假设编号从0开始)时,我们至多只能找到n-i个点共线。假设此前找到的共线的点的数量的最大值为k,如果有k \geq n-i,那么此时我们即可停止枚举,因为不可能再找到更大的答案了。

class Solution {
public:
    int gcd(int a, int b) {
        return b ? gcd(b, a % b) : a;
    }

    int maxPoints(vector<vector<int>>& points) {
        int n = points.size();
        if (n <= 2) {
            return n;
        }
        int ret = 0;
        for (int i = 0; i < n; i++) {
            if (ret >= n - i || ret > n / 2) {
                break;
            }
            unordered_map<int, int> mp;
            for (int j = i + 1; j < n; j++) {
                int x = points[i][0] - points[j][0];
                int y = points[i][1] - points[j][1];
                if (x == 0) {
                    y = 1;
                } else if (y == 0) {
                    x = 1;
                } else {
                    if (y < 0) {
                        x = -x;
                        y = -y;
                    }
                    int gcdXY = gcd(abs(x), abs(y));
                    x /= gcdXY, y /= gcdXY;
                }
                mp[y + x * 20001]++;
            }
            int maxn = 0;
            for (auto& [_, num] : mp) {
                maxn = max(maxn, num + 1);
            }
            ret = max(ret, maxn);
        }
        return ret;
    }
};

复杂度分析

时间复杂度:O(n^2 \times \log m),其中n为点的数量,m为横纵坐标差的最大值。最坏情况下我们需要枚举所有n个点,枚举单个点过程中需要进行O(n)次最大公约数计算,单次最大公约数计算的时间复杂度是O(\log m),因此总时间复杂度为O(n^2 \times \log m)

空间复杂度:O(n),其中n为点的数量。主要为哈希表的开销。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wanderer001

ROIAlign原理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值