paypal笔试: 关联用户(并查集)

题目

题目链接

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]]

题解

本题采用了并查集的思想,需要为同一组的用户指定一个统一的“组长”。每一组根据“组长”来区分。

对应到数组中就是每一个用户用数组索引来表示,元素值表示其父节点(即“组长”)。

paypal笔试: 关联用户(并查集)_第1张图片
如图,按照上述的说明,此数组中用户之间的分组关系就是:

[0, 5]
[1, 3]
[2, 4]

当遍历到某个位置时,用当前位置的用户来和前面的已分组用户进行判断,如果二者之间的距离判断为“关联”,那么就将此用户与已分组用户“联合”,将其归为已分组用户所属组,如果不关联,那么当前位置用户自己为一个组,且自己就是“组长”。

这里面有一些需要注意的地方。

  1. 在进行“联合”时,注意联合的是组,而不是用户。如果将用户A归为用户B所在的组(简称A组合B组),那么需要将当前所有用户中属于A组的用户全部归为B组,而不是仅仅将当前用户划过去。

  2. 再进行联合分组时,将组长索引大的归为组长号小的那一组。这样做的目的是在输出所有组的时候是有序输出,符合题意。


//采用并查集思想,将所有的关联节点父子关系使用数组索引表示

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);
    }
}

你可能感兴趣的:(剑指offer)