Description
牛客网 2018校招真题 直线上的点
Solving Ideas
https://leetcode.com/problems/max-points-on-a-line/
https://leetcode.com/problems/max-points-on-a-line/discuss/47113/A-java-solution-with-notes
https://www.jianshu.com/p/317c676f9e00
基本思路:
因为两点确定一条直线,所以可以遍历所有直线,并在遍历的过程中,把已遍历的直线缓存起来,当遇到的相同直线时,就把这条直线上存在的点的个数累加,这样就可以求得经过某个点的某条直线上最多的点的个数。
如何遍历所有的直线?
两层循环遍历所有直线。假设有数组Point[] points
,则
for (int i = 0; i < points.length; i++) {
...
for (int j = i + 1; j < points.length; j++) {
...
}
...
}
外层循环:表示遍历经过points[i]
这个点的所有可能的直线
内层循环:表示由points[i]
和points[j]
这两点确定的直线
因为由(points[i], points[j])
确定的直线 与 由(points[j], points[i])
确定的直线是相同,和两点的顺序无关,为了避免重复计算,所以内层循环将j
初始化为i + 1
。
如何判断直线是否相同?
对于二维的情况,如果两条直线斜率相等且都通过某一个定点,那么可以判定这两条直线相等。
例如平面内有点A、B、C,直线AB的斜率为k1,直线AC的斜率为k2,因为k1=k2且两直线都通过点A,所以AB和AC是一条直线,即A、B、C三点共线。
对于三维的情况,同样可以采用类似的方法来进行判定,只不过空间中用的是向量描述而不是斜率。
对于空间中两点
A
(
x
1
,
y
1
,
z
1
)
A(x_1, y_1, z_1)
A(x1,y1,z1)和
B
(
x
2
,
y
2
,
z
2
)
B(x_2, y_2, z_2)
B(x2,y2,z2),则AB的方向向量为
(
x
2
−
x
1
,
y
2
−
y
1
,
z
2
−
z
1
)
(x_2-x_1, y_2-y_1, z_2-z_1)
(x2−x1,y2−y1,z2−z1),由两点式可得AB直线方程为:
x
−
x
1
x
2
−
x
1
=
y
−
y
1
y
2
−
y
1
=
z
−
z
1
z
2
−
z
1
\frac{x-x_1}{x_2-x_1}=\frac{y-y_1}{y_2-y_1}=\frac{z-z_1}{z_2-z_1}
x2−x1x−x1=y2−y1y−y1=z2−z1z−z1
对于点
A
(
x
1
,
y
1
,
z
1
)
A(x_1, y_1, z_1)
A(x1,y1,z1)和
C
(
x
3
,
y
3
,
z
3
)
C(x_3, y_3, z_3)
C(x3,y3,z3),则AC的方向向量为
(
x
3
−
x
1
,
y
3
−
y
1
,
z
3
−
z
1
)
(x_3-x_1, y_3-y_1, z_3-z_1)
(x3−x1,y3−y1,z3−z1),AC直线方程为:
x
−
x
1
x
3
−
x
1
=
y
−
y
1
y
3
−
y
1
=
z
−
z
1
z
3
−
z
1
\frac{x-x_1}{x_3-x_1}=\frac{y-y_1}{y_3-y_1}=\frac{z-z_1}{z_3-z_1}
x3−x1x−x1=y3−y1y−y1=z3−z1z−z1
当AB的方向向量 ( x 2 − x 1 , y 2 − y 1 , z 2 − z 1 ) (x_2-x_1, y_2-y_1, z_2-z_1) (x2−x1,y2−y1,z2−z1)与AC的方向向量 ( x 3 − x 1 , y 3 − y 1 , z 3 − z 1 ) (x_3-x_1, y_3-y_1, z_3-z_1) (x3−x1,y3−y1,z3−z1)平行时,AB、AC共线,此时两条直线方程是相等的。
简单来说,对于空间中的两条直线,如果方向向量平行,且都通过某一个定点,即方向向量共线时,则可以判定这两条直线相等。
特殊情况处理:
遍历时,外层循环的点当做定点,当内层循环的点与该定点重叠时,则将其累加到该定点确定的所有直线的点数上。
Solution
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
class Point {
int x;
int y;
int z;
Point(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}
/**
* @author wylu
*/
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
Point[] points = new Point[n];
for (int i = 0; i < n; i++) {
String[] strs = br.readLine().split(" ");
points[i] = new Point(Integer.parseInt(strs[0]), Integer.parseInt(strs[1]), Integer.parseInt(strs[2]));
}
System.out.println(maxPoints(points));
}
private static int maxPoints(Point[] points) {
if (points.length <= 2) return points.length;
Map<Integer, Map<Integer, Map <Integer, Integer>>> map = new HashMap<>();
int res = 0;
for (int i = 0; i < points.length; i++) {
int overlap = 0, count = 0;
for (int j = i + 1; j < points.length; j++) {
//此处不取绝对值是因为下方通过约分可以保证其转为正整数
int dx = points[i].x - points[j].x;
int dy = points[i].y - points[j].y;
int dz = points[i].z - points[j].z;
if (dx == 0 && dy == 0 && dz == 0) {
overlap++;
continue;
}
// (2,2,2)与(1,1,1)平行,在这里因为都通过points[i],所以(2,2,2)与(1,1,1)共线
int gcd = gcd(dx, dy, dz);
dx /= gcd;
dy /= gcd;
dz /= gcd;
if (map.containsKey(dx)) {
if (map.get(dx).containsKey(dy)) {
if (map.get(dx).get(dy).containsKey(dz)) {
map.get(dx).get(dy).put(dz, map.get(dx).get(dy).get(dz) + 1);
} else {
map.get(dx).get(dy).put(dz, 1);
}
} else {
Map<Integer, Integer> m = new HashMap<>();
m.put(dz, 1);
map.get(dx).put(dy, m);
}
} else {
Map<Integer, Integer> m1 = new HashMap<>();
m1.put(dz, 1);
Map<Integer, Map<Integer, Integer>> m2 = new HashMap<>();
m2.put(dy, m1);
map.put(dx, m2);
}
count = Math.max(count, map.get(dx).get(dy).get(dz));
}
res = Math.max(res, count + 1 + overlap);
//如果下一个定点的直线的方向向量与当前定点的直线的方向向量平行时,
//内层循环将会判定这两个方向向量共线,但实际上有可能并不共线,所以必须清除缓存
map.clear();
}
return res;
}
private static int gcd(int a, int b, int c) {
return gcd(a, gcd(b, c));
}
private static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}