Объединить сообщества и найти размер самого большого из них

#java #algorithm #linked-list #disjoint-sets

#java #алгоритм #связанный список #непересекающиеся множества

Вопрос:

Я борюсь с этой проблемой на HackerRank. https://www.hackerrank.com/challenges/friend-circle-queries/problem Я попытался решить это с помощью пользовательского связанного списка — NodeList. В нем есть три поля — Node first, Node current, int size. «добавить» — это перегруженный метод. Это может добавить значение или другой список узлов. Я поместил код для NodeList в комментарии, потому что это не имеет большого значения.

Поля :-

 static HashMap<Integer, Integer> personToIndex = new HashMap<>();
static int largestCircleSize = 0;
static ArrayList<NodeList> groups = new ArrayList<>();
  

Это мой метод бизнес-логики.
Когда только один человек входит в круг друзей, я добавляю другого человека в круг. Когда оба человека, которые обмениваются рукопожатием, уже являются членами других кругов, я объединяю круги.

 static void updateFriendCircles(int friend1, int friend2) {
    int friend1Index, friend2Index;
    NodeList toUpdate;
    friend1Index = personToIndex.getOrDefault(friend1, -1);
    friend2Index = personToIndex.getOrDefault(friend2, -1);
    if (friend1Index != -1) {
        NodeList list = groups.get(friend1Index);
        if (friend2Index != -1) {
            NodeList list2 = groups.get(friend2Index);
            if (list.first == groups.get(friend2Index).first)
                return;
            toUpdate = list.add(list2);
            groups.set(friend2Index, list);
        }
        else {
            toUpdate = list.add(friend2);
            personToIndex.put(friend2, friend1Index);
        }
    }
    else if (friend2Index != -1) {
        toUpdate = groups.get(friend2Index).add(friend1);
        personToIndex.put(friend1, friend2Index);
    }
    else {
        int index = groups.size();
        personToIndex.put(friend1, index);
        personToIndex.put(friend2, index);
        toUpdate = new NodeList(friend1).add(friend2);
        groups.add(toUpdate);
    }
    if (toUpdate.size > largestCircleSize)
        largestCircleSize = toUpdate.size;
}
  

Я также пробовал использовать HashSet, но у него тоже такая же проблема, поэтому я думаю, что проблема не в структуре данных.

Ответ №1:

Поскольку неясно, что именно не так с решением (это не указано в OP) — неправильные ответы или тайм-аут для некоторых тестовых случаев, я объясню, как это решить.

Мы можем использовать структуру данных непересекающегося набора для представления наборов кругов друзей.

Основная идея заключается в том, что в каждом круге мы назначаем член, который используется для представления данного круга. Мы можем назвать это root. Поиск количества участников в круге всегда делегируется корню, который хранит его размер.

Каждый некорневой участник указывает на своего корневого участника или на участника, через которого он может получить доступ к корню. В будущем текущий корень также может потерять свой статус root для сообщества, но тогда он будет указывать на новый корень, и поэтому к нему всегда можно добраться с помощью связанных вызовов.

Когда 2 круги сливаются, новый корневой участник выбирается среди 2 предыдущих. В него можно задать новый размер, поскольку предыдущие корни уже содержат размеры для обоих кругов. Но как выбирается новый root? Если размер circle 1 не меньше, чем у circle 2 , то он выбирается в качестве нового корня.

Итак, для решения этой проблемы сначала мы должны определить заполнители для circles и sizes :

 Map<Integer, Integer> people;
Map<Integer, Integer> sizes;
  

Для некорневых участников в people key — это идентификатор пользователя, а value — друг, на которого он подписан (root или родительский, который может привести его к root). У корневых участников не будет записи на карте.

Затем нам нужен метод, который приведет нас к корню для любого участника:

 int findCommunityRoot(int x) {
    if (people.containsKey(x)) {
        return findCommunityRoot(people.get(x));
    }
    return x;
}
  

Наконец, нам нужен метод для создания сообщества для 2 заданных друзей:

 int mergeCommunities(int x, int y) {
    //find a root of x
    x = findCommunityRoot(x);
    //find a root of y
    y = findCommunityRoot(y);

    // one-man circle has a size of 1
    if (!sizes.containsKey(x)) {
        sizes.put(x, 1);
    }
    // one-man circle has a size of 1
    if (!sizes.containsKey(y)) {
        sizes.put(y, 1);
    }

    // friends in the same circle so just return its size
    if (x == y) {
        return sizes.get(x);
    }

    sizeX = sizes.get(x);
    sizeY = sizes.get(y);
    if (sizeX >= sizeY) { 
        people.put(y, x);
        sizes.put(x, sizeY   sizeX); 
        return sizes.get(x);
    } else {
        people.put(x, y);
        sizes.put(y, sizeY   sizeX); 
        return sizes.get(y);
    }
}
  

Итак, у нас есть все необходимое для сохранения размера самого большого круга на каждой итерации:

 List<Integer> maxCircle(int[][] queries) {
    List<Integer> maxCircles = new ArrayList<>();

    int maxSize = 1;
    for (int i = 0; i < queries.length; i  ) {
        int size = mergeCommunities(queries[i][0], queries[i][1]);
        maxSize = Math.max(maxSize, size);
        maxCircles.add(maxSize);
    }

    return maxCircles;
}