Найти дубликаты в массиве - PullRequest
23 голосов
/ 14 августа 2011

Учитывая массив из n целочисленных элементов, как вы узнаете, есть ли дубликаты в массиве за O (n) времени без использования дополнительного пространства.

С дополнительным пробелом это означает дополнительный пробел порядка O (n).

Помогает ли оператор Xor каким-либо образом.

Ответы [ 13 ]

23 голосов
/ 14 августа 2011

Если нет никакой дополнительной информации, этот вопрос кажется неразрешимым, так как это Проблема Различия Элементов , которая не разрешима с предоставленными вами ограничениями в требуемое время.

Вы можете разрешить:

(1) больше памяти и использовать хеш-таблицу / хэш-сет и соответствовать O(n) временные критерии.[выполнить итерацию массива, проверить, есть ли элемент в хеш-таблице, есть ли у вас дубликаты, в противном случае - вставить элемент в таблицу и продолжить].

(2) больше времени , сортируйте массив [O (nlogn)] и удовлетворяйте критерию сублинейного пространства.[После сортировки переберите массив и для каждого a[i] , a[i+1] проверьте, идентичны ли они.Если вы не нашли одинаковую пару, у вас нет дупликов]

EDIT : Доказательство этого утверждения немного длинное и требует математической записи, которая здесь не поддерживается (sidenote:нам действительно нужна поддержка tex), но идея в том, что если мы смоделируем нашу задачу как дерево алгебраических вычислений (что является справедливым допущением, когда хеширование не разрешено, а постоянное пространство не доступно), то Бен Ор доказал в своей статье Нижние границы для деревьев алгебраических вычислений (1983) (опубликовано в престижном ACM), отличимость этого элемента является проблемой Omega(nlogn) в этой модели.Любив показал, что тот же вывод применим и при ограничении себя целочисленными значениями в 1991 году: Нижняя граница для задачи разграничения целочисленных элементов , но в этих статьях делается вывод о том, что в модели вычисления алгебраического дерева - проблема целочисленной разноститакое Омега (нлогн) Проблема .

7 голосов
/ 17 апреля 2015

Сортировка по радиусу на месте с последующим линейным сканированием

Алгоритм сортировки по месту

В зависимости от того, что вы на самом деле считаете сложностью по времени сортировки по радиксу, это решение O (N) время, хотя мое личное мнение не так.Я думаю, что если вы не сделаете предположение о линейном времени для целочисленной сортировки, то проблема неразрешима.

Из-за того, что сортировка на месте, есть только O (1) дополнительнотребуется хранилище.

Весь код C ++ 11

Шаг 1: Сортировка по радикалу на месте

template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void RecurseOnRadixSort(std::vector<T>& myArray, T mask, int zerosEnd, int onesBegin)
{
    if (zerosEnd+1 >= onesBegin-1 || mask == 0) 
        return;

    int zerosEnd2 = zerosEnd;
    int onesBegin2 = onesBegin;
    while(zerosEnd2+1 <= onesBegin2-1)
    {
        // swap ones to the right
        if ((myArray[zerosEnd2+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd2+1], myArray[onesBegin2-1]);
            --onesBegin2;
        }
        else
            ++zerosEnd2;
    }

    mask >>= 1;

    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, zerosEnd, zerosEnd2+1);

    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin2-1, onesBegin);
}

template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void InPlaceRadixSort(std::vector<T>& myArray)
{
    int zerosEnd = -1;
    int onesBegin = static_cast<int>(myArray.size());
    T mask = static_cast<T>(1) << sizeof(T)*8-1;
    while(zerosEnd+1 <= onesBegin-1)
    {
        if ( (myArray[zerosEnd+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd+1], myArray[onesBegin-1]);
            --onesBegin;
        }
        else
            ++zerosEnd;
    }

    mask = static_cast<T>(1) << sizeof(T)*8-2; // need to reassign in case of signed datatype
    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, -1, zerosEnd+1);
    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin-1, static_cast<int>(myArray.size()));

    // swap negatives to the front
    auto iterSmallest = std::min_element(myArray.begin(), myArray.end());
    if (*iterSmallest < 0)
    {
        std::reverse(myArray.begin(), myArray.end());
        iterSmallest = std::min_element(myArray.begin(), myArray.end());
        std::reverse(myArray.begin(), iterSmallest+1);
        std::reverse(iterSmallest+1, myArray.end());
    }
}

Шаг 2: Линейное сканирование на наличие дублирующих элементов

for (size_t i=0, j=1; j<myArray.size(); ++i,++j)
{
    if (myArray[i] == myArray[j])
    {
        std::cout << "Found duplicate element " << myArray[i];
    }
}

Полный код

#include <iostream>
#include <string>
#include <vector>
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <type_traits>
using namespace std;
#define N 10

template <typename T>
void PrintArray(const std::vector<T>& myArray)
{
    for (auto&& element : myArray)
    {
        std::cout << element << std::endl;
    }
}

template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void RecurseOnRadixSort(std::vector<T>& myArray, T mask, int zerosEnd, int onesBegin)
{
    if (zerosEnd+1 >= onesBegin-1 || mask == 0) 
        return;

    int zerosEnd2 = zerosEnd;
    int onesBegin2 = onesBegin;
    while(zerosEnd2+1 <= onesBegin2-1)
    {
        // swap ones to the right
        if ((myArray[zerosEnd2+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd2+1], myArray[onesBegin2-1]);
            --onesBegin2;
        }
        else
            ++zerosEnd2;
    }

    mask >>= 1;

    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, zerosEnd, zerosEnd2+1);

    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin2-1, onesBegin);
}

template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void InPlaceRadixSort(std::vector<T>& myArray)
{
    int zerosEnd = -1;
    int onesBegin = static_cast<int>(myArray.size());
    T mask = static_cast<T>(1) << sizeof(T)*8-1;
    while(zerosEnd+1 <= onesBegin-1)
    {
        if ( (myArray[zerosEnd+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd+1], myArray[onesBegin-1]);
            --onesBegin;
        }
        else
            ++zerosEnd;
    }

    mask = static_cast<T>(1) << sizeof(T)*8-2; // need to reassign in case of signed datatype
    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, -1, zerosEnd+1);
    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin-1, static_cast<int>(myArray.size()));

    // swap negatives to the front
    auto iterSmallest = std::min_element(myArray.begin(), myArray.end());
    if (*iterSmallest < 0)
    {
        std::reverse(myArray.begin(), myArray.end());
        iterSmallest = std::min_element(myArray.begin(), myArray.end());
        std::reverse(myArray.begin(), iterSmallest+1);
        std::reverse(iterSmallest+1, myArray.end());
    }
}

int main() {
    srand(time(NULL));
    std::vector<int> myArray(N);
    for (size_t i=0;i<myArray.size();++i)
    {
        myArray[i] = rand() % 100 * (rand() % 2 == 1?-1:1);
    }

    std::cout << "Vector before radix sort: " << std::endl;
    PrintArray(myArray);
    InPlaceRadixSort(myArray);
    std::cout << "Vector after radix sort: " << std::endl;
    PrintArray(myArray);

    for (size_t i=0, j=1; j<myArray.size(); ++i,++j)
    {
        if (myArray[i] == myArray[j])
        {
            std::cout << "Found duplicate element " << myArray[i];
        }
    }
    return 0;
}

Демонстрационная версия

4 голосов
/ 16 августа 2011

Вот интересное решение этой проблемы с одним ограничением, что элементы должны находиться в диапазоне от 0 до n-2 (включительно), где n - количество элементов.

Это работаетза O (n) время с O (1) пространственной сложностью.

3 голосов
/ 18 октября 2012

Вот решение с использованием O (n) времени и O (1) пространства!

Traverse the array. Do following for every index i of A[].
{
    check for sign of A[abs(A[i])] ;
    if positive then        make it negative by   A[abs(A[i])]=-A[abs(A[i])];
    else  // i.e., A[abs(A[i])] is negative
    this   element (ith element of list) is a repetition
}

Кредиты: метод 5 Компьютерщик для Компьютерщиков

2 голосов
/ 17 апреля 2015

Это решение основано на решении, которое удаляет дубликаты из массива с помощью @dsimcha, как можно найти здесь .

Он выполняет алгоритм замены на месте с использованием хэшей значенийпоменять позиции.Обратите внимание, что это в некоторой степени уничтожает исходное содержимое массива.Но в вопросе OP не было требования, запрещающего это.

public static class DupFinder
{
    public static bool HasDups(int[] array, ref int nEvals)
    {
        nEvals = 0;
        return DupFinder.FindInPlace(array, 0, ref nEvals);
    }

    private static bool FindInPlace(int[] array, int start, ref int nEvals)
    {
        if (array.Length - start < 2)
            return false;

        var sentinel = array[start];
        var offset = start + 1;
        var len = array.Length - offset;
        for (var ndx = 0; ndx < len; nEvals++)
        {
            var cur = array[offset + ndx];
            if (cur == sentinel)
            {
                ndx++;
                continue;
            }

            var hash = cur % len;
            if (ndx == hash)
            {
                ndx++;
                continue;
            }

            var at_hash = array[offset + hash];
            if (cur == at_hash)
            {
                array[offset + ndx] = sentinel;
                ndx++;
                continue;
            }

            if (at_hash == sentinel)
            {
                Swap(array, offset, ndx, hash);
                ndx++;
                continue;
            }

            var hash_hash = at_hash % len;
            if (hash_hash != hash)
            {
                Swap(array, offset, ndx, hash);
                if (hash < ndx)
                    ndx++;
            }
            else
            {
                ndx++;
            }
        }

        var swapPos = 0;
        for (var i = 0; i < len; i++, nEvals++)
        {
            var cur = array[offset + i];
            if (cur != sentinel && i == (cur % len))
                Swap(array, offset, i, swapPos++);
        }

        for (var i = swapPos; i < len; nEvals++)
        {
            var cur = array[offset + i];
            if (cur == sentinel)
                return true; // got dups.
            else
                i++;
        }

        // Let's assume C# supports tail recursion ;-)
        // Then => look ma, O(1) extra storage space.
        return FindInPlace(array, offset + swapPos, ref nEvals);
    }

    private static void Swap(int[] array, int offset, int first, int second)
    {
        var tmp = array[offset + first];
        array[offset + first] = array[offset + second];
        array[offset + second] = tmp;
    }
}

Таким образом, если мы на мгновение предположим, что c # поддерживает хвостовую рекурсию, и мы не считаем использованные кадры стека как дополнительное пространство, тоO (1) требования к пространству.

Автор упоминает, что он имеет O (N) -овременную сложность.Проведенные мной (ограниченные) тесты (в отличие от анализа сложности вычислений) показали бы, что он ближе к O (N log N).

Array Size   Dup Position    #Evals
12           7               26
12           -               35
100,000      80,000          279,997
100,000      -               453,441
2 голосов
/ 16 апреля 2015

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

Понятно, что вам нужно хотя бы N шагов, чтобы увидеть всевход.Так что он не может быть быстрее , чем O(n).

Теперь, чтобы убедиться, что вы нашли все возможные дубликаты, у вас есть разные возможности:

  • Сравните каждыйчисло с любым другим числом, это не требует много дополнительного пространства, но занимает O(n^2) время`
  • Сделайте сравнение более умным способом, поменяв местами целые числа в доступном пространстве.Это позволяет «хранить информацию» в самой последовательности.На самом деле, сравнение всех чисел обычно выполняется в алгоритмах сортировки .Самые быстрые из известных алгоритмов сортировки, которые не требуют дополнительного места, требуют O(n log n) времени. В Википедии довольно длинная рецензия с большим количеством источников .Таким образом, вы никогда не сможете правильно рассчитать время.( сравнительная таблица известных алгоритмов сортировки )
  • Вы можете вести бухгалтерский учет с помощью хэш-карты, которая может позволить вам брать только линейное время O(n), но для этого нужны бухгалтерские операциихраниться где-то .В противном случае вы просто «забудете», какие цифры вы уже видели.К сожалению, бухгалтерия потребует больше места, если ваш ввод увеличится, потому что у вас есть так много разных номеров, которые нужно запомнить.Таким образом, невозможно иметь одинаковый фиксированный объем памяти и сравнивать произвольно длинные входные последовательности.Следовательно, вам придется нарушать постоянное пространство O(1).

Как @Atishay указывает в своем ответе, может быть решением, если у вас очень ограниченный ввод,Здесь требуется, чтобы у вас был массив размером n, а возможные значения были только в диапазоне [0,n-2].Это требование гарантирует, что где-то ДОЛЖЕН быть дубликат, поскольку в массиве меньше значений, чем элементов.С этим знанием и очень конкретным диапазоном ценностей, вы можете сделать это.Но это использует очень узкие допущения и не решает общую проблему, изложенную в вопросе.

Редактировать

Как поясняется в комментариях, существует доказанная нижняя граница для временной сложности сравнения.основанные алгоритмы сортировки.Для справки см. Здесь:

1 голос
/ 12 мая 2017

Чистый пример для определения дубликатов с O (n) по времени и O (1) по пробелу:

public class DuplicateDetermineAlgorithm {
    public static boolean isContainsDuplicate(int[] array) {
        if (array == null) {
            throw new IllegalArgumentException("Input array can not be null");
        }
        if (array.length < 2) {
            return false;
        }

        for (int i = 0; i < array.length; i++) {
            int pointer = convertToPositive(array[i]) - 1;
            if (array[pointer] > 0) {
                array[pointer] = changeSign(array[pointer]);
            } else {
                return true;
            }
        }
        return false;
    }

    private static int convertToPositive(int value) {
        return value < 0 ? changeSign(value) : value;
    }

    private static int changeSign(int value) {
        return -1 * value;
    }
}
1 голос
/ 14 августа 2011

Фильтр Блума - это хэш-набор с эффективным использованием пространства с настраиваемой частотой ложных срабатываний.Ложная положительная вероятность означает, что вы должны вернуться и проверить наличие дубликата, когда получаете удар от BF, вводя термин N ^ 2 - но коэффициент равен ~ exp (- (дополнительное пространство используется для фильтра)).Это создает интересное пространство в сравнении с пространством компромисса времени.

У меня нет доказательства того, что поставленный вопрос неразрешим, но в целом «вот интересное пространство компромисса» - хороший ответ на неразрешимую проблему.

1 голос
/ 14 августа 2011

реализация, использующая один int как временную переменную .. это использует битовые векторы /

 public static boolean isUniqueChars(String str) {
    int checker = 0;
    for (int i = 0; i < str.length(); ++i) {
     int val = str.charAt(i) - ‘a’;
     if ((checker & (1 << val)) > 0) return false;
     checker |= (1 << val);
    }
    return true;
  }

или мою предыдущую реализацию O (n ^ 2) без использования какой-либо временной переменной

public static bool isDuplicate(char[] str) {
    if (str == null) return false;
    int len = str.length;
    if (len < 2) return false;

    for (int i = 1; i < len; ++i) {
      for (int j = 0; j < len; ++j) {
        if (str[i] == str[j]) return true;
      }
    }
    return false;
  }
0 голосов
/ 08 февраля 2019

Я хотел бы поставить на стол следующее решение. Я надеюсь, что это может помочь.

assert(A.size() > 1);

int m = A[0];
for(unsigned int i = 1; i < A.size(); ++i) {    // O(n)
    m ^= A[i];
    m ~= m;
}
return m;

Он основан на: https://codesays.com/2015/solution-to-odd-occurrences-in-array-by-codility/

Приветствия

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...