编程题 | 直线上最多的点数(from leetcode)

题目描述

给定一个二维平面,平面上有 个点,求最多有多少个点在同一条直线上。

示例 1:

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4

示例 2:

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6

思路: 

解法一:

用y = kx + b 来表示直线,遍历每一条直线,针对每一条直线然后遍历统计直线上的点。

很冗长的C++.... 而且写的时候把k和b这两个数用分子和分母来表示显得很不友好。

//分数
struct FenShu{
    int a=1;
    int b=1;
    FenShu(int a, int b) : a(a), b(b) {}
};

FenShu k={0,1};
FenShu b ={0,1};
//二维平面直线函数 y = kx + b
bool flag = false;
int x = 0;
void get_k_b(Point & point1, Point & point2){
    if((double)(point1.x - point2.x) != 0){
        k.b = point1.y - point2.y;
        k.a = point1.x - point2.x;
        b.a = k.a;
        b.b = point1.y * k.a - point1.x * k.b;
        flag = false;
    } else{
        x = point1.x;
        flag = true;
    }

}
//是否在直线上
bool onLine(Point & point){
    if(flag){
        return point.x == x;
    }
    //用long long 防止Int 越界
    long long left = (long long)point.y * (long long)k.a;
    long long right = (long long)point.x * k.b + (long long)b.b;
    return left == right;
}
int max_76(int x, int y){
    return x > y ? x : y;
}
/**
 * 给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上
 * @param points
 * @return
 */
int maxPoints(vector<Point>& points) {
    if(points.size() <=2 ){
        return points.size();
    }
    int max = 0;
    for(int i = 0; i < points.size()-1; i++){
        for(int j = i+1; j < points.size(); j++){
            get_k_b(points[i],points[j]); //两点确定一条直线
            int subMax = 0; //有多少个点在该直线上
            for(int a = 0; a < points.size(); a++){
                if(onLine(points[a])){
                    subMax++;
                }
            }
            max = max_76(max, subMax);
            if(subMax == points.size()){
                return max;
            }

        }
    }
    return  max;
}

解法二:  

这种解法就高级了很多!  首先让我们明确一个道理:两个不相同的点确定一条直线,我们应该遍历所有的直线。

如果有n个点的话,那么共有n(n-1)/2 条直线,那么程序里面应该怎么写呢?难道是 

for(int i = 0; i < n; i++)

{

    for(int j =0; j < n; j++)

    {

        if( j != i)  {........}

    }

}

这样的写法显然会有重复。所以要改进,实际上,上述问题也是n个点求两两组合的问题。举个简单的例子: a b c d e 五个点,我们可以这样遍历:ab ac ad ae bc bd be cd ce de  . 所以正确的框架为:  

for(int i=0; i<n-1; i++)    
{
    for(int j=i+1; j<n; j++)  {}
}

在本题中因为是取两点,然后确定之后的点是否在这两点构成的直线上,所以区间各减一,也就是

for(int i=0; i<n-2; i++)
{
    for(int j=i+1; j<n-1)
    {
        for(int k=j+1; k<n; k++)  {验证k是否在有i j构成的直线上}
    }
}

接下来,再考虑一下这道题的特殊情况,就是第i点的后面有可能有与i点相同的点(重复点),所以这时候要把i更新为最后的那个i点,同样的对j遍历的时候也是一样的。 

So, 力推第二种解法!!!

int maxPoints(vector<Point>& points) {
        sort(points.begin(),points.end(),cmp);  //按照cmp规则对数组排序
        if(points.size()<3)
            return points.size();  //0 1 2的情况
    
        int ans=2,count1=1,count2=1;
        for(int i=0;i<points.size()-2;i++){  //i的区间是从第一个点到倒数第三个点   
            count1=1;
            while(i<points.size()-3 && points[i+1].x==points[i].x && points[i+1].y==points[i].y){
                count1++;   //重复点处理,count1就是当前的i重复点的个数    
                i++;
            }
            for(int j=i+1;j<points.size()-1;j++){
                count2=1;
                while(j<points.size()-2 && points[j+1].x==points[j].x && points[j+1].y==points[j].y){
                    count2++;   //重复点处理,当前j的重复点的个数 
                    j++;
                }
                int tmp=count1+count2;  //由i j构成的这条直线上已经有了这么多点
                for(int k=j+1;k<points.size();k++){
                    if(isLine(points[i],points[j],points[k]))
                        tmp++;
                }
                ans=ans>tmp?ans:tmp;  // ans = max(ans, tmp);   
            }
        }
        return ans;
    }
    static bool cmp(Point& a,Point& b){   //注意这里:sort的比较函数必须写在类外部(即全局区域)或者声明为静态函数
        if(a.x!=b.x)
            return a.x<b.x;  //按照点的横坐标进行排序 
        else
            return a.y<b.y;
    }
    bool isLine(Point& a,Point& b,Point& c){
        long long tmp1=((long long)a.x-b.x)*((long long)a.y-c.y);   //可能超出int的范围 所有用 long long
        long long tmp2=((long long)a.x-c.x)*((long long)a.y-b.y);
        //  这里注意三个点P1(x1,y1) P2(x2, y2) P3(x3, y3)  共线满足 (y1-y2)/(x1-x2) == (y1-y3)/(x1-x3) 分母可能为0 故直接改为
        // 乘法: (y1-y3)*(x1-x2)==(y1-y2)*(x1-x3) 就是共线的充要条件   
        return tmp1 == tmp2 ? true : false;    
    }

解法三: 

现学现卖 Java版   

class Solution {
    public int maxPoints(Point[] points) {
        if(points.length == 0) return 0;
        if(points.length == 1) return 1;
        if(points.length == 2) return 2;
        int ans = 2;
        int Max = -1;
        for(int i=0;i<points.length;i++){
            for(int j=i+1;j<points.length;j++){
                ans = 2;
                for(int k=0;k<points.length;k++){
                    if(k == i || k == j) continue;
                    int x1 = points[i].x;
                    int y1 = points[i].y;
                    int x2 = points[j].x;
                    int y2 = points[j].y;
                    int x3 = points[k].x;
                    int y3 = points[k].y;
                    if(x1 == x2){
                        if(y1 == y2) {
                            if( x2 == x3 && y2 == y3){
                                ans++;
                            }else{
                                continue;
                            }
                        }else {
                            if(x2 == x3)
                                ans++;
                        }
                    }
                    else{
                        long tanK1 = ((long)x3-x1)*((long)y2-y1);
                        long tanK2 = ((long)y3-y1)*((long)x2-x1);
                        if(tanK1 == tanK2) ans++;
                    }
                }
                if(Max < ans) Max = ans;
            }
        }
        return Max;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值