[H枚举] lc149. 直线上最多的点数(枚举+细节优化+数学)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[H枚举] lc149. 直线上最多的点数(枚举+细节优化+数学)相关的知识,希望对你有一定的参考价值。
1. 题目来源
大佬题解:直线一般式,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=0等价于x(y2−y1)+y(x1−x2)+x1(y1−y2)+y1(x2−x1)=0
其可以与平面上的直线一一对应,但是不同的 A , B , C A,B,C A,B,C 可能存在倍数关系,即可能出现直线共线的情况。所以需要对 A , B , C A,B,C A,B,C 约掉其最大公约数,保证直线唯一。
极其优质:
- 直线一般式,C++ STL 应用
- 实际上只需要一般式中的
A、B
即可表示一个直线
当使用 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;
}
};
大佬的写法:
用 array
用 tuple
均可。类似于这种类型、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. 直线上最多的点数(枚举+细节优化+数学)的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode第149题—直线上最多的点数—Python实现