蓝桥杯 算法训练 共线(Java)

该问题探讨了在二维平面上,通过n个整点的最大直线数量。通过数学中的点斜式,可以判断一个点是否在已知直线上的方法。文章提到了一个初始的解决方案,即遍历所有点对来生成直线,但这种方法会导致超时。为提高效率,作者将除法操作替换为乘法,避免了精度损失并解决了运算速度问题。最终的优化算法通过遍历和比较斜率的乘积来确定最多能过的点数。
摘要由CSDN通过智能技术生成

问题描述:

给定2维平面上n个整点的坐标,一条直线最多能过几个点?

输入格式:

 第一行一个整数n表示点的个数
 以下n行,每行2个整数分别表示每个点的x,y坐标。

输出格式:

 输出一个整数表示答案。

样例输入:

5
0 0
1 1
2 2
0 3
2 3

样例输出:

3

思路:

  1. 在初中的数学中,我们都知道两点可以确定一条直线,而怎么判断另外一个点是否在直线上呢,其实我们可以通过已知直线的一点和另外一个点通过点斜式的方式 :

( y1 - y0) / ( x1 - x0) = ( y1 - y 3) / ( x1 - x3)

其中红色绿色是代表原先构成直线的两个点,而黑色的则是代码第3个点,我们通过判断第三点和构成直线的其中一点的斜率是否相同这里可能有小伙伴就会问了,如果那两条直线的斜率相等,但是是平行不相交的关系怎么办呢?其实不用担心,因为第三个点和直线的其中一点新构成的直线必定是会与本来的那条是重合的关系的,这时如果斜率不相同,那么两根线的必定不会重合,也就是那根线就不会在原来的直线上。

  1. 继续观察,我们就拿题目中的例子来看,5个x,y的坐标点,我们需要两两组合依次成为新的直线。

static boolean vit[];
for (int i = 0; i < N; i++){
            if (!vit[i]){
                vit[i] = true;
}
for (int k = i + 1; k < N; k++){
                    vit[k] = true;
}

         temp = Math.max(temp,count);
         count = 0;
         vit[k] = false;
System.out.println(temp + 2);
}

因此我使用了布尔类型的一维数组,把遍历的点都标记为true,然后内嵌套一层循环把这个点以下的点都遍历一次。遍历完成后把内层循环的点的布尔值重新设置为false,方便外层遍历的时候使用。最后我把每次的情况都与上一次进行比较,把大的留下,因为我在计算遍历的时候是除去了原来的构成直线的两点,因此在输出的时候还需要加上2。

  1. 但是我按照上面的思路发现写的时候,发现运算超时了!!!

上源码:
 double a = (arr[k][1] - arr[i][1]) / (arr[k][0] - arr[i][0]);

                    for (int  j = 0; j < N; j++){
                        if (!vit[j]){
                            if ( (arr[j][1] - arr[i][1]) / (arr[j][0] - arr[i][0]) == a ){
                                count++;
                            }
                        }
                    }

而我觉得很大问题就出现在这个,程序运算进行除法时,哪怕你声明成为double类型,先比较与int类型而言,运算更快,但是如果是还是这样进行运算的话,会出现精度的损失!!!因为int会直接取消小数点后面的数值 这时1.5也等于1.4了。

因此我在上面的那条等式中改变了思路;

(y1 - y0) / (x1 - x0) = (y1 - y 3) / (x1 - x3) 等价于 (y1 - y0) * (x1 - x3) = (y1 - y 3) * (x1 - x0)

这样换成乘法问题就得到了解决啦。

完整代码:

import java.util.Scanner;

public class Main {
    static int N;
    static boolean vit[];
    static int arr[][];
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);

        N = scan.nextInt();
        arr = new int[N+1][2];
        vit = new boolean[N];
        for (int i = 0 ; i < N; i++){
            for (int j = 0; j < arr[i].length; j++){
                arr[i][j] = scan.nextInt();
            }
        }
        dfs();

    }



    public static void dfs(){

        int count = 0;
        int temp = 0;
        for (int i = 0; i < N; i++){
            if (!vit[i]){
                vit[i] = true;

                for (int k = i + 1; k < N; k++){
                    vit[k] = true;

                    int dy = arr[k][1] - arr[i][1];
                    int dx = arr[k][0] - arr[i][0];

                    for (int  j = 0; j < N; j++){
                        if (!vit[j]){
                            if ( (arr[j][1] - arr[i][1])  *  dx ==  (arr[j][0] - arr[i][0]) * dy ){
                                count++;
                            }
                        }
                    }

                    temp = Math.max(temp,count);
                    count = 0;
                    vit[k] = false;
                }
            }
        }
        System.out.println(temp + 2);
    }

}

测试结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luca-s-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值