Подтвердить что ты не робот

Более элегантный способ проверки дубликатов в массиве С++?

Я написал этот код на С++ как часть задачи uni, где мне нужно убедиться, что в массиве нет дубликатов:

// Check for duplicate numbers in user inputted data
    int i; // Need to declare i here so that it can be accessed by the 'inner' loop that starts on line 21
    for(i = 0;i < 6; i++) { // Check each other number in the array
        for(int j = i; j < 6; j++) { // Check the rest of the numbers
            if(j != i) { // Makes sure don't check number against itself
                if(userNumbers[i] == userNumbers[j]) {
                    b = true;
                }
            }
            if(b == true) { // If there is a duplicate, change that particular number
                cout << "Please re-enter number " << i + 1 << ". Duplicate numbers are not allowed:" << endl;
                cin >> userNumbers[i];
            }
        } // Comparison loop
        b = false; // Reset the boolean after each number entered has been checked
    } // Main check loop

Он работает отлично, но я хотел бы знать, есть ли более элегантный или эффективный способ проверки.

4b9b3361

Ответ 1

Вы можете отсортировать массив в O (nlog (n)), а затем просто посмотреть до следующего числа. Это существенно быстрее, чем ваш существующий алгоритм O (n ^ 2). Код также намного чище. Ваш код также не гарантирует, что дубликаты не были вставлены после их повторного ввода. Прежде всего необходимо предотвратить дублирование существующих.

std::sort(userNumbers.begin(), userNumbers.end());
for(int i = 0; i < userNumbers.size() - 1; i++) {
    if (userNumbers[i] == userNumbers[i + 1]) {
        userNumbers.erase(userNumbers.begin() + i);
        i--;
    }
}

Я также повторю рекомендацию использовать std:: set - нет дубликатов там.

Ответ 2

Следующее решение основано на сортировке чисел и последующем удалении дубликатов:

#include <algorithm>

int main()
{
    int userNumbers[6];

    // ...

    int* end = userNumbers + 6;
    std::sort(userNumbers, end);
    bool containsDuplicates = (std::unique(userNumbers, end) != end);
}

Ответ 3

В самом деле, самый быстрый и насколько я могу видеть самый изящный метод, как указано выше:

std::vector<int> tUserNumbers;
// ...
std::set<int> tSet(tUserNumbers.begin(), tUserNumbers.end());
std::vector<int>(tSet.begin(), tSet.end()).swap(tUserNumbers);

Это O (n log n). Это, однако, не делает этого, если необходимо упорядочить числа во входном массиве... В этом случае я сделал:

    std::set<int> tTmp;
    std::vector<int>::iterator tNewEnd = 
        std::remove_if(tUserNumbers.begin(), tUserNumbers.end(), 
        [&tTmp] (int pNumber) -> bool {
            return (!tTmp.insert(pNumber).second);
    });
    tUserNumbers.erase(tNewEnd, tUserNumbers.end());

который все еще O (n log n) и сохраняет исходный порядок элементов в tUserNumbers.

Приветствия,

Пол

Ответ 4

Вы можете добавить все элементы в набор и проверить при добавлении, если он уже присутствует или нет. Это было бы более элегантно и эффективно.

Ответ 5

Я не уверен, почему это не было предложено, но вот способ в базе 10 найти дубликаты в O (n). Проблема, которую я вижу с уже предложенным решением O (n), заключается в том, что она требует что сначала сортируются цифры. Этот метод является O (n) и не требует сортировки набора. Самое замечательное в том, что проверка того, имеет ли конкретная цифра дубликаты, - это O (1). Я знаю, что эта ветка, вероятно, мертва, но, возможно, это поможет кому-то!:)

/*
============================
Foo
============================
* 
   Takes in a read only unsigned int. A table is created to store counters 
   for each digit. If any digit counter is flipped higher than 1, function
   returns. For example, with 48778584:
    0   1   2   3   4   5   6   7   8   9
   [0] [0] [0] [0] [2] [1] [0] [2] [2] [0]

   When we iterate over this array, we find that 4 is duplicated and immediately
   return false.

*/
bool Foo( unsigned const int &number)
{
    int temp = number;
    int digitTable[10]={0};

    while(temp > 0)
    {
        digitTable[temp % 10]++; // Last digit respective index.
        temp /= 10; // Move to next digit
    }

    for (int i=0; i < 10; i++)
    {
        if (digitTable [i] > 1)
        {
            return false;
        }
    }
    return true;
}

Ответ 6

В расширении ответа отвечает @Puppy, который является лучшим ответом.

PS: Я попытался вставить это сообщение как комментарий в текущий лучший ответ от @Puppy, но не мог, так как у меня еще нет 50 очков. Кроме того, для получения дополнительной информации здесь приводится несколько экспериментальных данных.

Оба std:: set и std:: map реализованы в STL, используя только дерево Balanced Binary Search. Таким образом, оба будут приводить к сложности O (nlogn) только в этом случае. В то время как лучшая производительность может быть достигнута, если используется хеш-таблица. std:: unordered_map предлагает хэш-таблицу на основе реализации для более быстрого поиска. Я экспериментировал со всеми тремя реализациями и нашел результаты, используя std:: unordered_map, лучше, чем std:: set и std:: map. Результаты и код приведены ниже. Изображения представляют собой снимок производительности, измеряемый LeetCode в решениях.

bool hasDuplicate(vector<int>& nums) {
    size_t count = nums.size();
    if (!count)
        return false;
    std::unordered_map<int, int> tbl;
    //std::set<int> tbl;
    for (size_t i = 0; i < count; i++) {
        if (tbl.find(nums[i]) != tbl.end())
            return true;
        tbl[nums[i]] = 1;
        //tbl.insert(nums[i]);
    }
    return false;
}

код > unordered_map Производительность (время работы здесь составляло 52 мс) введите описание изображения здесь

Установить/Карта Производительность введите описание изображения здесь

Ответ 7

Это нормально, особенно для небольших массивов. Я бы использовал более эффективные aproaches (меньше, чем n ^ 2/2 сравнения), если массив будет больше - см. Ответ DeadMG.

Некоторые небольшие исправления для вашего кода:

  • Вместо int j = i напишите int j = i +1, и вы можете опустить тест if(j != i)
  • Вам не нужно объявлять переменную i вне инструкции for.

Ответ 8

//std::unique(_copy) requires a sorted container.
std::sort(cont.begin(), cont.end());

//testing if cont has duplicates
std::unique(cont.begin(), cont.end()) != cont.end();

//getting a new container with no duplicates
std::unique_copy(cont.begin(), cont.end(), std::back_inserter(cont2));

Ответ 9

#include<iostream>
#include<algorithm>

int main(){

    int arr[] = {3, 2, 3, 4, 1, 5, 5, 5};
    int len = sizeof(arr) / sizeof(*arr); // Finding length of array

    std::sort(arr, arr+len);

    int unique_elements = std::unique(arr, arr+len) - arr;

    if(unique_elements == len) std::cout << "Duplicate number is not present here\n";
    else std::cout << "Duplicate number present in this array\n";

    return 0;
}

Ответ 10

Как упомянуто @underscore_d, элегантное и эффективное решение будет

#include <algorithm>
#include <vector>

template <class Iterator>
bool has_duplicates(Iterator begin, Iterator end) {
    using T = typename std::iterator_traits<Iterator>::value_type;
    std::vector<T> values(begin, end);

    std::sort(values.begin(), values.end());
    return (std::adjacent_find(values.begin(), values.end()) != values.end());
}

int main() {
    int user_ids[6];
    // ...
    std::cout << has_duplicates(user_ids, user_ids + 6) << std::endl;
}

Ответ 11

Я думаю, что решение @Michael Jaison G действительно блестящее, я немного модифицирую его код, чтобы избежать сортировки. (При использовании unordered_set алгоритм может немного ускориться.)

template <class Iterator>
bool isDuplicated(Iterator begin, Iterator end) {
    using T = typename std::iterator_traits<Iterator>::value_type;
    std::unordered_set<T> values(begin, end);
    std::size_t size = std::distance(begin,end);
    return size != values.size();
}