LeetCode 149. Max Points on a Line
题目描述
Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.
题目分析
题目大意是给出平面上 n 个点的坐标, 求出这其中最多有多少个点在同一条直线上。
首先进行复杂度下界的分析。 首先由两点构成一条线, 因此一般情况下直线数目达到
分析过后思路也很明显, 就是如何把处理每一条直线的复杂度尽可能降低。 这道题目中, 处理一条直线实际上就是比较当前直线与其他直线是否处在一条直线上。如果我们能“记住”每一条直线, 每次处理一条直线的时候就将该直线加入对应的分类里,那么事情就变得简单了。那么回忆中学时期学过的知识:
Ax+By+C=0
粗略的说,每一个三元组
A,B,C
确定一条唯一的直线。当然这个条件并不充分。严格的说应该是:
每两条相同的直线,其方程都一定能转化成相同的Ax+By+C=0的形式。
如果这样的话,那么只需要用一个map<Line, int>
来记录下每一条直线出现的数目, 即可。Line
结构体需要对每一条不同的直线进行唯一标识, 由上面分析可知在Line
结构体中利用 A, B, C
三元组标识即可。
前面说过, 直接比较
A,B,C
并不可取, 因为如果其存在倍数关系, 事实上也是相同的。为了避免这种情况, 我们可以每次构造Line
结构体时,先在其构造函数里面进行如下处理:
1. 若
A
系数为负, 则将
2. 将
A,B,C
除以
gcd(A,gcd(B,C))
. 确保这个三元组无公因子。
这样处理之后, 就可以确认:对于处理过后每一组不同的 A,B,C 对应一条不同的直线。
大体思路就是这样, 下面给出详细代码:
/**
* Definition for a point.
* struct Point {
* int x;
* int y;
* Point() : x(0), y(0) {}
* Point(int a, int b) : x(a), y(b) {}
* };
*/
inline int gcd(int a, int b) {
int tmp, temp = a;
if (a < 0) a*= -1;
if (b < 0) b*= -1;
if (a < b) swap(a, b);
while (b != 0) {
tmp = b;
b = a % b;
a = tmp;
}
if (a != 0) return a;
else return temp;
}
inline void deal(int &A, int &B, int &C) {
int tmp = gcd(A, gcd(B, C));
if (tmp != 0) {
A/= tmp;
B/= tmp;
C/= tmp;
}
if (A < 0 || (A == 0 && B < 0) || (A == 0 && B == 0 && C < 0)) {
A*= -1;
B*= -1;
C*= -1;
//规定开头为正。
}
}
struct Line{
int A, B, C;
Line() {
}
Line(int a, int b, int c) {
A = a;
B = b;
C = c;
}
Line(Point &a, Point &b) {
A = (a.y - b.y);
B = (b.x - a.x);
C = b.y * a.x - b.x * a.y;
deal(A, B, C);
}
bool operator < (const Line &l)const
{
return (A < l.A || (A == l.A && B < l.B) || (A == l.A && B == l.B && C < l.C));
}
};
class Solution {
public:
int maxPoints(vector<Point>& points) {
if (points.size() <= 1) return points.size();
int max = 0, localmax = 0;
for (int i = 0; i < points.size() - 1; ++i) {
map<Line, int> count;
int overlap = 0;
localmax = 0;
for (int j = i + 1; j < points.size(); ++j) {
Line temp(points[i], points[j]);
if (points[i].x == points[j].x && points[i].y == points[j].y) {
++overlap;
}
else {
count[temp]++;
}
if (count[temp] > localmax) localmax = count[temp];
}
if (localmax + overlap > max) max = localmax + overlap;
}
cout << max << endl;
return max + 1;
}
};
其他细节
- 上面的分析只是算法的大体思路, 实际过程中还有很多细节要处理。 比如 A=0 时, 要先确保 B 的符号一致等。
- 即使运用上面的方法, 直接对
n2 条直线进行处理也是不可取的。 因为这样无法处理重复点的情况。考虑到一条直线,上面的每个点出现了多次, 那么我们将无法确切统计到底重复了多少点。 - 按照起点分类处理是一个很好的办法。 这样做可以确保统计的时候只会漏算当前点的重复点数量, 而这个值我们可以处理的时候用一个变量记住即可。在上面的程序里采用的就是这样一种办法,
localmax, overlap
正是做的这些工作。