题目
PayPal上海团队一直致力于风险控制,风控需要收集各种信息,有时需要通过地理位置找出用户与用户之间存在的关联关系,这一信息可能会用于找出用户潜在存在的风险问题。我们记两个用户的关联关系可以表示为:
(1) user1,user2与他们最常发生交易的地理位置分别为(x1, y1),(x2, y2),当这两个用户的欧氏距离不超过d时,我们就认为两个用户关联。
(2) 用户关联性具有传递性,若用户1与用户2关联,用户2与用户3关联,那么用户1,2,3均关联。
给定N个用户及其地理位置坐标,将用户按照关联性进行划分,要求返回一个集合,集合中每个元素是属于同一个范围的用户群。
输入描述:
d:欧式距离
N:用户数
之后的N行表示第0个用户到第N-1个用户的地理位置坐标
输出描述:
一个数组集合,所有关联的用户在一个数组中。
输出数组需要按照从小到大的顺序排序,每个集合内的数组也需要按照从小到大的顺序排序。
输入例子1:
2.0
5
3.0 5.0
6.0 13.0
2.0 6.0
7.0 12.0
0.0 2.0
输出例子1:
[[0, 2], [1, 3], [4]]
题解
本题采用了并查集的思想,需要为同一组的用户指定一个统一的“组长”。每一组根据“组长”来区分。
对应到数组中就是每一个用户用数组索引来表示,元素值表示其父节点(即“组长”)。
如图,按照上述的说明,此数组中用户之间的分组关系就是:
[0, 5]
[1, 3]
[2, 4]
当遍历到某个位置时,用当前位置的用户来和前面的已分组用户进行判断,如果二者之间的距离判断为“关联”,那么就将此用户与已分组用户“联合”,将其归为已分组用户所属组,如果不关联,那么当前位置用户自己为一个组,且自己就是“组长”。
这里面有一些需要注意的地方。
-
在进行“联合”时,注意联合的是组,而不是用户。如果将用户A归为用户B所在的组(简称A组合B组),那么需要将当前所有用户中属于A组的用户全部归为B组,而不是仅仅将当前用户划过去。
-
再进行联合分组时,将组长索引大的归为组长号小的那一组。这样做的目的是在输出所有组的时候是有序输出,符合题意。
//采用并查集思想,将所有的关联节点父子关系使用数组索引表示
import java.util.*;
public class Main{
static int[] users;
static double d; //欧式距离
//判断相连
private static boolean isConnected(double x1, double y1, double x2, double y2){
double dis = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); //计算两点距离
return dis <= d;
}
//获取当前节点的根节点
private static int getRoot(int i){
return users[i];
}
//合并p、q,将q的根节点设置为q的根节点
private static void union(int p, int q){
int pRoot = getRoot(p);
int qRoot = getRoot(q);
if (pRoot < qRoot) {
for (int i = 0; i < users.length; ++i) {
if (users[i] == qRoot) {
users[i] = pRoot;
}
}
} else {
for (int i = 0; i < users.length; ++i) {
if (users[i] == pRoot) {
users[i] = qRoot;
}
}
}
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
d = sc.nextDouble();
int n = sc.nextInt();
users = new int[n];
for( int i=0; i<n; ++i ){
users[i] = i;
}
double[][] pos = new double[n][2];
//读取位置信息,存入二维数组
for( int i=0; i<n; ++i ){
pos[i][0] = sc.nextDouble();
pos[i][1] = sc.nextDouble();
}
//遍历数组,建立联系
for( int i=0; i<n; ++i ){
for( int j=0; j<i; ++j ){
if( isConnected(pos[i][0], pos[i][1], pos[j][0], pos[j][1]) ) {
union(i, j);
}
}
}
//将有联系的用户放进集合
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
for( int i=0; i<n; ++i ){
if( users[i] == i ){
//如果这是一个根节点,建立一个关于她的数组集合
ArrayList<Integer> temp = new ArrayList<>();
for( int j=i; j<n; ++j ){
if( users[j]==i ) temp.add(j);
}
res.add(temp);
}
}
System.out.println(res);
}
}