https://oj.leetcode.com/problems/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.
public int maxPoints(Point[] points)
一条直线的表达方式有好几种,一般比较常见的就是y = kx + c 或者ax + by + c = 0
一种O(N^2)的做法是基于某一个起始点,然后和其他点进行比较,对于每一个起始点,我们维护一个哈希表,键放的东西是用来表达斜率k或者a和b,因为都是同一个起始点,所以起始就完全不需要考虑作为偏移量的常量C了。所以简单来说,在这种做法里,因为每两个点都可以得到一个k或者a和b,在不考虑偏移量的情况下,斜率相同就可以被认为是同一条直线上的。然后遍历所有点作为起始点就可以了。
基于这个算法,需要解决的就是如何在哈希表里去表达这个斜率了。每一个起始点都有自己的哈希表,键放的是斜率的表达方式,值放的就是一个结果数量。在这个过程里找到最大的结果数量并返回就可以。如上面所说,表达式有两种, y = kx + c,或者 ax + by + c = 0。 c我们已经不用管了。那么就有起码两种解决方式了。
第一种,键值那里放的是一个k,double值。这个k的求值方法为(y2 - y1)/(x2 - x1),这里要处理的边界情况有两个,首先显而易见的是x2 == x1的情况。所以我们除了哈希表以外,还需要一个临时变量去处理这个,然后第二个边界情况是不那么显而易见的,那就是y2 == y1的情况。事实上本身这个情况是不那么需要处理的。但是在Java里(c++里没验证过),Double值0和-0是不一样的。所以即使y2 == y1,x2 > x1和x1 > x2时上述表达式求出的斜率是不同的。。这样就错了。所以这也是一个特殊情况。还有一个最特殊的情况就是重复点。判断完这三个特殊情况就可以进行正常的判断了。给出代码如下:
public int maxPoints(Point[] points) {
int res = 0;
for(int i = 0; i < points.length; i++){
HashMap<Double, Integer> k_counter = new HashMap<Double, Integer>();
int same_x = 0, same_y = 0, same_point = 1;
int cur_res = 0;
for(int j = i + 1; j < points.length; j++){
if(points[j].x == points[i].x && points[i].y == points[j].y){
same_point++;
}else if(points[j].x == points[i].x){
same_x++;
}else if(points[j].y == points[i].y){
same_y++;// same_y 必须要的原因是在double里,0.0 和 -0.0是不一样的。所以Wrong Answer最后一个case无法过就是如此。
}else{
Double k = (points[j].y - points[i].y) / (double)(points[j].x - points[i].x);
k_counter.put(k, k_counter.containsKey(k) ? k_counter.get(k) + 1 : 1);
cur_res = Math.max(cur_res, k_counter.get(k));
}
}
res = Math.max(res, Math.max(cur_res, Math.max(same_x, same_y)) + same_point);
}
return res;
}
第二种就是用一个String来表达ax + by + c = 0中的a和b。这种情况会比上一个做法少一点边界情况的处理,但并不代表不需要处理。
根据表达式的求值方法,ax + by也可以表达为(y1 - y2)x + (x2 - x1)y。也就是实际上我们用y1 - y2和x1 - x2两个值来替换一个k的表达。可是这里我们再来看看一个情况。
x + 2y + 3 = 0 和 2x + 4y + 6 = 0其实是在表达同一条直线,只是第二个公式还需要化简才能转换成第一个表达式。所以实际上我们还需要得到y1 - y2 和 x1 - x2的最大公约数,然后将y1 - y2和x1 - x2同时除以这个公约数,再存放才具备参考价值。关于公约数的求法,参见代码吧。
public int gcd(int a, int b){//这个就是求最大公约数的函数
return (a != 0) ? gcd(b % a, a) : b;
}
public int maxPoints(Point[] points) {
int result = 0;
for(int i = 0; i < points.length; i++){
HashMap<String, Integer> map = new HashMap<String, Integer>();
int same_points = 1;
int curmax = 0;
for(int j = i + 1; j < points.length; j++){
if(points[i].x == points[j].x && points[i].y == points[j].y){
same_points++;
}else{
int diffx = points[i].x - points[j].x;
int diffy = points[i].y - points[j].y;
int gcd = gcd(diffx, diffy);
if(diffx * gcd < 0)
gcd *= -1;
diffx = gcd == 0 ? diffx : diffx / gcd;
diffy = gcd == 0 ? diffy : diffy / gcd;
String key = diffx + " " + diffy;
if(map.containsKey(key)){
int cur_line = map.get(key) + 1;
curmax = Math.max(cur_line, curmax);
map.put(key, cur_line);
}else{
map.put(key, 1);
curmax = Math.max(curmax, 1);
}
}
}
result = Math.max(result, curmax + same_points);
}
return result;
}
2018-01-27 Updated:
Leetcode更新了,如今解法1已经没办法通过最后一个case了。相信leetcode也意识到了一个问题,double和float是存在误差的。所以如果继续用y = kx + c的方式,如果在斜率相差极小的情况下,double的值是会被判定相同也就是会认为是同一条斜线。所以此时只能用最大公约数的方式得到ax + by + c = 0存整型数a和b了。再给一段代码吧:
public int gcd(int a, int b) {
return a != 0 ? gcd(b % a, a) : b;
}
public int maxPoints(Point[] points) {
int result = 0;
for (int i = 0; i < points.length; i++) {
int sameXCount = 1, sameYCount = 1, samePoints = 0, kResult = 1;
Map<String, Integer> kCountMap = new HashMap<String, Integer>();
for (int j = i + 1; j < points.length; j++) {
if (points[i].x == points[j].x && points[i].y == points[j].y) {
samePoints++;
} else if (points[i].x == points[j].x) {
sameXCount++;
} else if (points[i].y == points[j].y) {
sameYCount++;
} else {
int diffX = points[i].x - points[j].x;
int diffY = points[i].y - points[j].y;
int gcd = gcd(Math.abs(diffX), Math.abs(diffY));
int a = (diffX / gcd) * (diffX < 0 ? -1 : 1);
int b = (diffY / gcd) * (diffX < 0 ? -1 : 1);
String key = a + " " + b;
int kCount = kCountMap.containsKey(key) ? kCountMap.get(key) + 1 : 2;
kCountMap.put(key, kCount);
kResult = Math.max(kResult, kCount);
}
}
result = Math.max(result, Math.max(sameXCount, Math.max(sameYCount, kResult)) + samePoints);
}
return result;
}