一、点集合确定的不同直线数
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。
给定平面上 2 × 3 个整点 {(x,y)|0 ≤ x < 2,0 ≤ y < 3, x ∈ Z,y ∈ Z},即横坐标是 0 到 1 (包含 0 和 1) 之间的整数、纵坐标是 0 到 2 (包含 0 和 2) 之间的整数的点。这些点一共确定了 11 条不同的直线。
给定平面上 20 × 21 个整点 {(x,y)|0 ≤ x < 20,0 ≤ y < 21, x ∈ Z,y ∈ Z},即横坐标是 0 到 19 (包含 0 和 19) 之间的整数、纵坐标是 0 到 20 (包含 0 和 20) 之间的整数的点。请问这些点一共确定了多少条不同的直线。
遍历两个点,采用直线方程:(y1-y2) * x +(x2-x1) * y +( x1 * y2 - x2 * y1)=0直线方程,最好采用这种方式,可以避免精度问题、分数问题,注意要求三个数的gcd,并约分,如果求直线条数可以直接装到set中。
上面的直线方程由两点式直线方程推出,即 (y - y2) / (y1 - y2) = (x - x2) / (x1 - x2)
二、直线上最多的点数
思路,遍历两个点,统计从每个点出发的k(斜率)的个数,只取出现次数最多的k,个数+1就 = 对当前点来说,一条直线最多能够经过的点的个数(这条直线一定包含当前点,这就是前面+1的原因),再从剩下的点出发,依次比较答案,取最大值即可。
class Solution {
public int maxPoints(int[][] points) {
int ans = 0;
for (int i = 0; i < points.length; i++) {
HashMap<String, Integer> map = new HashMap<>();
// 对于当前点来说,一条直线最多经过的点的个数
int max = 0;
for (int j = i + 1; j < points.length; j++) {
int x1 = points[i][0], y1 = points[i][1];
int x2 = points[j][0], y2 = points[j][1];
int up = y2 - y1;
int down = x2 - x1;
int tmp = gcd(up, down);
up /= tmp;
down /= tmp;
String str = up + "_" + down;
map.put(str, map.getOrDefault(str, 0) + 1);
max = Math.max(max, map.get(str));
}
// 别忘了自己本身也算一个点
ans = Math.max(ans, max + 1);
}
return ans;
}
public int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
}
三、平面切分
【问题描述】
平面上有 N 条直线,其中第 i 条直线是 y = Ai · x + Bi。
请计算这些直线将平面分成了几个部分。
【输入格式】
第一行包含一个整数 N。
以下 N 行,每行包含两个整数 Ai; Bi。
【输出格式】
一个整数代表答案。
【样例输入】
3
1 1
2 2
3 3
1
2
3
4
【样例输出】
6
1
【评测用例规模与约定】
对于 50% 的评测用例, 1 ≤ N ≤ 4, −10 ≤ Ai; Bi ≤ 10。
对于所有评测用例, 1 ≤ N ≤ 1000, −100000 ≤ Ai; Bi ≤ 100000。
平面中只有一条直线时(划分两个平面),如果增加一条平行的直线,划分的平面数 + 1;如果增加的一条直线是相交的,划分的平面数 + 2 = 4。再增加一条线与之前两条线相交,平面数 + 3 = 7。
规律:每增加一条直线,增加的平面数 = 该直线与之前直线的交点数 + 1
需要注意的是,可能有重复边的情况,要去重,这里选择直接对坐标点去重,更加方便。
题目中直接告诉了斜率和截距,那就很方便,不用再求k了。
y = A1X + B1,y = A2X + B2,交点,x = (B2 - B1) / (A1 - A2),y = A1X + B1或者y = A2X + B2都可以。
四、消灭老鼠
用(y1-y2) * x +(x2-x1) * y +( x1 * y2 - x2 * y1)=0直线方程来表示,找不同直线的条数,注意去重,注意使用gcd对三数约分。
import java.util.HashSet;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
// 激光枪所在位置
int x2 = scan.nextInt();
int y2 = scan.nextInt();
int[] mouses = new int[n];
// 计算当前点与与激光枪形成直线,求直线条数即可(去重)
HashSet<String> set = new HashSet<>();
// (y1 - y2) * x + (x2 - x1) * y + (x1 * y2 - x2 * y1) = 0
for (int i = 0; i < n; i++) {
int x1 = scan.nextInt();
int y1 = scan.nextInt();
int a = y1 - y2;
int b = x2 - x1;
int c = x1 * y2 - x2 * y1;
// 三数约分
int div = gcd(a, gcd(b, c));
a /= div;
b /= div;
c /= div;
String tmp = a + "x" + "+" + b + "y" + "+" + c;
set.add(tmp);
}
System.out.println(set.size());
}
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}
如果题目中要求站在原点,遍历不同的方向,那就需要把四个象限的k值分开考虑,此时最好的方法时, 求gcd时,都使用绝对值,这样得到的最大公约数都是正数,可以分别为每个象限记录k值。