Max Points on a Line

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;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值