圆形靶内的最大飞镖数量
题目
墙壁上挂着一个圆形的飞镖靶。现在请你蒙着眼睛向靶上投掷飞镖。
投掷到墙上的飞镖用二维平面上的点坐标数组表示。飞镖靶的半径为 r 。
请返回能够落在 任意 半径为 r 的圆形靶内或靶上的最大飞镖数。
数据规模:点的个数N<=100,r<=5000
对题目进行解析,就是要找一个圆心,使得以该圆心为中心、r为半径的圆能够覆盖(在圆内或者圆上)更多的给定的点。求能覆盖的最多的点的数目。
思路
由于圆心是在二维坐标平面,是无限可能的,所以不能直接用暴力的方法。
换一种思路,如果找到这个圆(这个圆能覆盖最多的点),那么一定满足至少有两个点在这个圆上。用反证法可以证明:
如果有0个点在这个圆上,可以通过移动圆心,使得有1个点在圆上,并且被覆盖的点不会变少。
如果有1个点在这个圆上,可以通过移动圆心,使得有2个点在圆上,并且被覆盖的点不会变少。可以通过固定这个点在圆上,然后进行旋转。
所以遍历两两的点,找到他们的圆心,求这个圆心覆盖的点的个数。
从圆上两个点找到圆心的公式是:
实现代码如下:
class Solution {
double precision=1e-10;
public int numPoints(int[][] points, int r) {
int n=points.length;
int max=1;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i==j){
continue;
}
double[] center=getCenter(i,j,points,r);
if(center==null){
continue;
}
int cnt=getCnt(center,r,points);
max=Math.max(max,cnt);
}
}
return max;
}
private boolean onOrIn(int[] p,double[] center,int r){
//发现有一个5.000000000000001
return getDist(new double[]{p[0],p[1]},center)-r<=precision;
}
private int getCnt(double[] center,int r,int[][] points){
int res=0;
for(int[] p:points){
if(onOrIn(p,center,r)){
res++;
}
}
return res;
}
private double[] getCenter(int i,int j,int[][] points,int r){
double[] p0=new double[]{points[i][0],points[i][1]};
double[] p1=new double[]{points[j][0],points[j][1]};
double dist=getDist(p0,p1);
if(dist>2*r){
return null;
}
double a=dist/2;
double h=Math.sqrt(r*r-a*a);
double[] OM=new double[]{(p0[0]+p1[0])/2.0,(p0[1]+p1[1])/2.0};
double[] MC=new double[]{p0[1]-p1[1],-(p0[0]-p1[0])};
double factor=h/getDist(MC,new double[]{0,0});
MC[0]*=factor;
MC[1]*=factor;
return new double[]{OM[0]+MC[0],OM[1]+MC[1]};
}
private double getDist(double[] p1,double[] p2){
double x=p1[0]-p2[0];
double y=p1[1]-p2[1];
return Math.sqrt(x*x+y*y);
}
}
还有另一种叫做Angular Sweep的算法,以后有空再看看。