it-roy-ru.com

Алгоритм: эффективный способ удаления дублирующихся целых чисел из массива

Я получил эту проблему из интервью с Microsoft.

Учитывая массив случайных целых чисел, написать алгоритм на C, который удаляет дублировать номера и вернуть уникальные номера в оригинале массив.

E.g Ввод: {4, 8, 4, 1, 1, 2, 9} Выход: {4, 8, 1, 2, 9, ?, ?}

Одно предупреждение: ожидаемый алгоритм не должен требовать сортировки массива в первую очередь. И когда элемент был удален, следующие элементы также должны быть сдвинуты вперед. В любом случае, значение элементов в конце массива, в котором элементы были сдвинуты вперед, ничтожно мало. 

Обновление: Результат должен быть возвращен в исходном массиве, а вспомогательная структура данных (например, хеш-таблица) не должна использоваться. Тем не менее, я думаю, что сохранение порядка не является необходимым.

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

84
ejel

Как насчет:

void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 )
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

Должно быть O (n ^ 2) или меньше.

18
mocj

Решение, предложенное моей девушкой, - это разновидность слияния. Единственное изменение заключается в том, что на этапе объединения просто игнорируйте дублирующиеся значения. Это решение будет также O (n log n). При таком подходе сортировка/удаление дубликатов объединяются вместе. Тем не менее, я не уверен, если это имеет какое-либо значение, хотя.

132
ejel

Я уже писал об этом раньше на SO, но я воспроизведу это здесь, потому что это довольно круто. Он использует хеширование, создавая что-то вроде хэша, установленного на месте. Он гарантированно находится O(1) в подмышечном пространстве (рекурсия - это хвостовой вызов), и, как правило, O(N) сложность по времени. Алгоритм выглядит следующим образом:

  1. Возьмите первый элемент массива, это будет страж.
  2. Переупорядочьте остальную часть массива, насколько это возможно, так, чтобы каждый элемент находился в позиции, соответствующей его хешу. По завершении этого шага будут обнаружены дубликаты. Установите их равными часовому.
  3. Переместить все элементы, для которых индекс равен хешу, в начало массива.
  4. Переместите все элементы, равные часовому, кроме первого элемента массива, в конец массива.
  5. То, что осталось между правильно хешированными элементами и дублирующими элементами, будет теми элементами, которые не могли быть помещены в индекс, соответствующий их хешу из-за столкновения. Рекурс, чтобы иметь дело с этими элементами.

Можно показать, что это O(N) при условии отсутствия патологического сценария в хешировании: даже если нет дубликатов, примерно 2/3 элементов будут удалены при каждой рекурсии. Каждый уровень рекурсии равен O(n), где small n - количество оставшихся элементов. Единственная проблема заключается в том, что на практике это медленнее, чем быстрая сортировка, когда имеется несколько дубликатов, то есть много коллизий. Однако, когда есть огромное количество дубликатов, это удивительно быстро.

Правка: в текущих реализациях D hash_t составляет 32 бита. Все, что касается этого алгоритма, предполагает, что в 32-битном пространстве будет очень мало коллизий хешей, если таковые имеются. Однако столкновения могут часто происходить в пространстве модулей. Однако это предположение, по всей вероятности, будет справедливо для любого набора данных разумного размера. Если ключ меньше или равен 32 битам, это может быть его собственный хэш, что означает, что конфликт в полном 32-битном пространстве невозможен. Если он больше, вы просто не сможете разместить их достаточно в адресном пространстве 32-битной памяти, чтобы это стало проблемой. Я предполагаю, что hash_t будет увеличен до 64 бит в 64-битных реализациях D, где наборы данных могут быть больше. Кроме того, если это когда-либо окажется проблемой, можно изменить хэш-функцию на каждом уровне рекурсии.

Вот реализация на языке программирования D:

void uniqueInPlace(T)(ref T[] dataIn) {
    uniqueInPlaceImpl(dataIn, 0);
}

void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
    if(dataIn.length - start < 2)
        return;

    invariant T sentinel = dataIn[start];
    T[] data = dataIn[start + 1..$];

    static hash_t getHash(T elem) {
        static if(is(T == uint) || is(T == int)) {
            return cast(hash_t) elem;
        } else static if(__traits(compiles, elem.toHash)) {
            return elem.toHash;
        } else {
            static auto ti = typeid(typeof(elem));
            return ti.getHash(&elem);
        }
    }

    for(size_t index = 0; index < data.length;) {
        if(data[index] == sentinel) {
            index++;
            continue;
        }

        auto hash = getHash(data[index]) % data.length;
        if(index == hash) {
            index++;
            continue;
        }

        if(data[index] == data[hash]) {
            data[index] = sentinel;
            index++;
            continue;
        }

        if(data[hash] == sentinel) {
            swap(data[hash], data[index]);
            index++;
            continue;
        }

        auto hashHash = getHash(data[hash]) % data.length;
        if(hashHash != hash) {
            swap(data[index], data[hash]);
            if(hash < index)
                index++;
        } else {
            index++;
        }
    }


    size_t swapPos = 0;
    foreach(i; 0..data.length) {
        if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
            swap(data[i], data[swapPos++]);
        }
    }

    size_t sentinelPos = data.length;
    for(size_t i = swapPos; i < sentinelPos;) {
        if(data[i] == sentinel) {
            swap(data[i], data[--sentinelPos]);
        } else {
            i++;
        }
    }

    dataIn = dataIn[0..sentinelPos + start + 1];
    uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}
45
dsimcha

Еще одна эффективная реализация 

int i, j;

/* new length of modified array */
int NewLength = 1;

for(i=1; i< Length; i++){

   for(j=0; j< NewLength ; j++)
   {

      if(array[i] == array[j])
      break;
   }

   /* if none of the values in index[0..j] of array is not same as array[i],
      then copy the current value to corresponding new position in array */

  if (j==NewLength )
      array[NewLength++] = array[i];
}

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

Выход этого кода - массив [] с размером NewLength

Здесь мы начинаем со 2-го элемента в массиве и сравниваем его со всеми элементами в массиве до этого массива . Мы держим дополнительную индексную переменную 'NewLength' для изменения входного массива . Инициализирована переменная NewLength до 0.

Элемент в массиве [1] будет сравниваться с массивом [0]. Если они разные, то значение в массиве [NewLength] будет изменено с помощью массива [1] и увеличено на NewLength. Если они одинаковые, NewLength не будет изменен.

Так что если у нас есть массив [1 2 1 3 1], Тогда

В первом проходе цикла 'j' массив [1] (2) будет сравниваться с массивом 0, затем 2 будет записан в массив [NewLength] = массив [1] , Так что массив будет [1 2] с момента NewLength = 2

Во втором проходе цикла 'j' массив [2] (1) будет сравниваться с массивами 0 и массивом 1. Здесь, так как array [2] (1) и array0 - один и тот же цикл, здесь прервется . Поэтому массив будет [1 2], так как NewLength = 2

и так далее

20
Byju

Если вы ищете старшую O-нотацию, тогда сортировка массива с помощью O (n log n) сортировки, тогда прохождение O(n) может быть лучшим маршрутом. Без сортировки вы смотрите на O (n ^ 2).

Правка: если вы просто делаете целые числа, то вы также можете сделать радикальную сортировку, чтобы получить O (n).

19
carl

1. Использование O(1) дополнительного пространства за O (n log n) времени

Это возможно, например:

  • сначала выполните сортировку на месте O (n log n)
  • затем пройтись по списку один раз, записав первый экземпляр каждого обратно в начало списка

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

2. Использование O(lots) дополнительного пространства в O(n) времени

  • объявить нулевой массив, достаточно большой, чтобы вместить все целые числа
  • пройти через массив один раз
  • установите соответствующий элемент массива в 1 для каждого целого числа.
  • Если это было уже 1, пропустите это целое число.

Это работает, только если выполняется несколько сомнительных предположений:

  • можно дешево обнулить память, или размер ints невелик по сравнению с их количеством
  • вы счастливы попросить у вашей ОС 256 ^ sizepof (int) памяти
  • и он действительно очень эффективно кеширует его, если он гигантский

Это плохой ответ, но если у вас есть много входных элементов, но все они 8-битные целые (или, может быть, даже 16-битные целые), это может быть лучшим способом.

3. O (маленький) -тиш лишний пробел, O (n) -иш-время

Как # 2, но используйте хеш-таблицу.

4. Ясный путь

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

Например. Пройдите по массиву для каждого уникального элемента (т.е. первого элемента, второго элемента (дубликаты первого были удалены) и т.д.), Удалив все идентичные элементы. O(1) дополнительный пробел, O (n ^ 2) время.

Например. Используйте библиотечные функции, которые делают это. эффективность зависит от того, что у вас легко доступно.

10
Jack V.

Ну, это базовая реализация довольно проста. Пройдите по всем элементам, проверьте, есть ли дубликаты в оставшихся, и переместите остальные поверх них.

Это ужасно неэффективно, и вы могли бы ускорить его с помощью вспомогательного массива для вывода или сортировки/двоичных деревьев, но это, по-видимому, недопустимо.

7
Dario

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

В Perl:

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        Push @newary, $i;
    }
}
6
Jeff B

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

Каждая итерация внешнего цикла обрабатывает один элемент массива. Если он уникален, он остается в начале массива, а если он является дубликатом, он перезаписывается последним необработанным элементом в массиве. Это решение выполняется за O (n ^ 2) времени.

#include <stdio.h>
#include <stdlib.h>

size_t rmdup(int *arr, size_t len)
{
  size_t prev = 0;
  size_t curr = 1;
  size_t last = len - 1;
  while (curr <= last) {
    for (prev = 0; prev < curr && arr[curr] != arr[prev]; ++prev);
    if (prev == curr) {
      ++curr;
    } else {
      arr[curr] = arr[last];
      --last;
    }
  }
  return curr;
}

void print_array(int *arr, size_t len)
{
  printf("{");
  size_t curr = 0;
  for (curr = 0; curr < len; ++curr) {
    if (curr > 0) printf(", ");
    printf("%d", arr[curr]);
  }
  printf("}");
}

int main()
{
  int arr[] = {4, 8, 4, 1, 1, 2, 9};
  printf("Before: ");
  size_t len = sizeof (arr) / sizeof (arr[0]);
  print_array(arr, len);
  len = rmdup(arr, len);
  printf("\nAfter: ");
  print_array(arr, len);
  printf("\n");
  return 0;
}
5
dsh

Если вам разрешено использовать C++, ответ на вызов std::sort, а затем вызов std::unique. Сложность по времени составляет O (N log N) для сортировки и O(N) для уникального обхода.

И если C++ отсутствует, то ничто не мешает написанию этих же алгоритмов на C.

5
fbrereto

Вот версия Java.

int[] removeDuplicate(int[] input){

        int arrayLen = input.length;
        for(int i=0;i<arrayLen;i++){
            for(int j = i+1; j< arrayLen ; j++){
                if(((input[i]^input[j]) == 0)){
                    input[j] = 0;
                }
                if((input[j]==0) && j<arrayLen-1){
                        input[j] = input[j+1];
                        input[j+1] = 0;
                    }               
            }
        }       
        return input;       
    }
4
Naren

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

Если у вас неограниченная память, вы можете выделить битовый массив для байтов sizeof(type-of-element-in-array) / 8, чтобы каждый бит показывал, встречали ли вы уже соответствующее значение или нет.

Если вы этого не сделаете, я не могу придумать ничего лучше, чем обойти массив и сравнить каждое значение со значениями, которые следуют за ним, а затем, если найден дубликат, полностью удалить эти значения. Это где-то рядом с O (n ^ 2) (или O ((n ^ 2-n)/2)).

У IBM есть статья на довольно близкую тему.

2
Anton Gogolev

Посмотрим:

  • O (N) проход, чтобы найти мин/макс выделить
  • битовый массив для найденного 
  • O (N) пройти обмен местами дубликатов до конца.
2
Douglas Leeder

Вот мое решение. 

///// find duplicates in an array and remove them

void unique(int* input, int n)
{
     merge_sort(input, 0, n) ;

     int prev = 0  ;

     for(int i = 1 ; i < n ; i++)
     {
          if(input[i] != input[prev])
               if(prev < i-1)
                   input[prev++] = input[i] ;                         
     }
}
2
octoback
import Java.util.ArrayList;


public class C {

    public static void main(String[] args) {

        int arr[] = {2,5,5,5,9,11,11,23,34,34,34,45,45};

        ArrayList<Integer> arr1 = new ArrayList<Integer>();

        for(int i=0;i<arr.length-1;i++){

            if(arr[i] == arr[i+1]){
                arr[i] = 99999;
            }
        }

        for(int i=0;i<arr.length;i++){
            if(arr[i] != 99999){

                arr1.add(arr[i]);
            }
        }

        System.out.println(arr1);
}
    }
1
Ankit Jain

Это наивное (N * (N-1)/2) решение. Он использует постоянное дополнительное пространство и поддерживает первоначальный порядок. Это похоже на решение @Byju, но не использует блоки if(){}. Это также позволяет избежать копирования элемента на себя.

#include <stdio.h>
#include <stdlib.h>

int numbers[] = {4, 8, 4, 1, 1, 2, 9};
#define COUNT (sizeof numbers / sizeof numbers[0])

size_t undup_it(int array[], size_t len)
{
size_t src,dst;

  /* an array of size=1 cannot contain duplicate values */
if (len <2) return len; 
  /* an array of size>1 will cannot at least one unique value */
for (src=dst=1; src < len; src++) {
        size_t cur;
        for (cur=0; cur < dst; cur++ ) {
                if (array[cur] == array[src]) break;
                }
        if (cur != dst) continue; /* found a duplicate */

                /* array[src] must be new: add it to the list of non-duplicates */
        if (dst < src) array[dst] = array[src]; /* avoid copy-to-self */
        dst++;
        }
return dst; /* number of valid alements in new array */
}

void print_it(int array[], size_t len)
{
size_t idx;

for (idx=0; idx < len; idx++)  {
        printf("%c %d", (idx) ? ',' :'{' , array[idx] );
        }
printf("}\n" );
}

int main(void) {    
    size_t cnt = COUNT;

    printf("Before undup:" );    
    print_it(numbers, cnt);    

    cnt = undup_it(numbers,cnt);

    printf("After undup:" );    
    print_it(numbers, cnt);

    return 0;
}
1
wildplasser

На Java я бы решил это так. Не знаю, как написать это на C.

   int length = array.length;
   for (int i = 0; i < length; i++) 
   {
      for (int j = i + 1; j < length; j++) 
      {
         if (array[i] == array[j]) 
         {
            int k, j;
            for (k = j + 1, l = j; k < length; k++, l++) 
            {
               if (array[k] != array[i]) 
               {
                  array[l] = array[k];
               }
               else
               {
                  l--;
               }
            }
            length = l;
         }
      }
   }
1
Dominik

После рассмотрения проблемы, вот мой способ Delphi, который может помочь

var
A: Array of Integer;
I,J,C,K, P: Integer;
begin
C:=10;
SetLength(A,10);
A[0]:=1; A[1]:=4; A[2]:=2; A[3]:=6; A[4]:=3; A[5]:=4;
A[6]:=3; A[7]:=4; A[8]:=2; A[9]:=5;

for I := 0 to C-1 do
begin
  for J := I+1 to C-1 do
    if A[I]=A[J] then
    begin
      for K := C-1 Downto J do
        if A[J]<>A[k] then
        begin
          P:=A[K];
          A[K]:=0;
          A[J]:=P;
          C:=K;
          break;
        end
        else
        begin
          A[K]:=0;
          C:=K;
        end;
    end;
end;

//tructate array
setlength(A,C);
end;
1
RichardLi

Это можно сделать за один проход с помощью алгоритма O (N log N) и без дополнительной памяти.

Перейдите от элемента a[1] к a[N]. На каждом этапе i все элементы слева от a[i] содержат отсортированную кучу элементов от a[0] до a[j]. Между тем, второй индекс j, изначально 0, отслеживает размер кучи.

Изучите a[i] и вставьте его в кучу, которая теперь занимает элементы от a[0] до a[j+1]. Поскольку элемент вставляется, если встречается дублирующий элемент a[k], имеющий то же значение, не вставляйте a[i] в кучу (т.е. отбрасывайте его); в противном случае вставьте его в кучу, которая теперь увеличивается на один элемент и теперь содержит от a[0] до a[j+1] и увеличивает j.

Продолжайте в том же духе, увеличивая i до тех пор, пока все элементы массива не будут проверены и вставлены в кучу, которая в конечном итоге занимает от a[0] до a[j]. j - это индекс последнего элемента кучи, и куча содержит только уникальные значения элементов.

int algorithm(int[] a, int n)
{
    int   i, j;  

    for (j = 0, i = 1;  i < n;  i++)
    {
        // Insert a[i] into the heap a[0...j]
        if (heapInsert(a, j, a[i]))
            j++;
    }
    return j;
}  

bool heapInsert(a[], int n, int val)
{
    // Insert val into heap a[0...n]
    ...code omitted for brevity...
    if (duplicate element a[k] == val)
        return false;
    a[k] = val;
    return true;
}

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

1
David R Tribble

Как насчет следующего?

int* temp = malloc(sizeof(int)*len);
int count = 0;
int x =0;
int y =0;
for(x=0;x<len;x++)
{
    for(y=0;y<count;y++)
    {
        if(*(temp+y)==*(array+x))
        {
            break;
        }
    }
    if(y==count)
    {
        *(temp+count) = *(array+x);
        count++;
    }
}
memcpy(array, temp, sizeof(int)*len);

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

1
Charith

Следующий пример должен решить вашу проблему:

def check_dump(x):
   if not x in t:
      t.append(x)
      return True

t=[]

output = filter(check_dump, input)

print(output)
True
1
yupbank

Это можно сделать за один проход, за O(N) время в количестве целых чисел во входном списке И в O(N) хранилище в количестве уникальных целых чисел.

Просмотрите список спереди назад, с двумя указателями «dst» и «Src», инициализированными для первого элемента. Начните с пустой хеш-таблицы .__ из "целых чисел". Если целое число в src отсутствует в хэше, Запишите его в слот в dst и увеличьте dst. Добавьте целое число в src к хешу, затем увеличьте src. Повторяйте, пока src не пройдет конец Списка ввода.

0
Andy Ross

В Java

    Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};

    String value ="";

    for(Integer i:arrayInteger)
    {
        if(!value.contains(Integer.toString(i))){
            value +=Integer.toString(i)+",";
        }

    }

    String[] arraySplitToString = value.split(",");
    Integer[] arrayIntResult = new Integer[arraySplitToString.length];
    for(int i = 0 ; i < arraySplitToString.length ; i++){
        arrayIntResult[i] = Integer.parseInt(arraySplitToString[i]);
    }

вывод: {1, 2, 3, 4, 6, 7, 8, 9, 10}

надеюсь, это поможет

0
PRABHU SEKAR

Используйте фильтр Блума для перемешивания. Это значительно уменьшит накладные расходы памяти.

0
gaurav gupta

Создайте BinarySearchTree со сложностью O(n).

0
cpp

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

Algorithm delete_duplicates (a[1....n])
//Remove duplicates from the given array 
//input parameters :a[1:n], an array of n elements.

{

temp[1:n]; //an array of n elements. 

temp[i]=a[i];for i=1 to n

 temp[i].value=a[i]

temp[i].key=i

 //based on 'value' sort the array temp.

//based on 'value' delete duplicate elements from temp.

//based on 'key' sort the array temp.//construct an array p using temp.

 p[i]=temp[i]value

  return p.

В других элементах поддерживается в выходном массиве с помощью «ключа». Предположим, ключ имеет длину O (n), время, необходимое для выполнения сортировки ключа, и значение равно O (nlogn). Таким образом, время, необходимое для удаления всех дубликатов из массива, составляет O (nlogn).

0
Sharief Muzammil

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

#include <stdio.h>
int main(void){
int x,n,myvar=0;
printf("Enter a number: \t");
scanf("%d",&n);
int arr[n],changedarr[n];

for(x=0;x<n;x++){
    printf("Enter a number for array[%d]: ",x);
    scanf("%d",&arr[x]);
}
printf("\nOriginal Number in an array\n");
for(x=0;x<n;x++){
    printf("%d\t",arr[x]);
}

int i=0,j=0;
// printf("i\tj\tarr\tchanged\n");

for (int i = 0; i < n; i++)
{
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    for (int j = 0; j <n; j++)
    {   
        if (i==j)
        {
            continue;

        }
        else if(arr[i]==arr[j]){
            changedarr[j]=0;

        }
        else{
            changedarr[i]=arr[i];

        }
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    }
    myvar+=1;
}
// printf("\n\nmyvar=%d\n",myvar);
int count=0;
printf("\nThe unique items:\n");
for (int i = 0; i < myvar; i++)
{
        if(changedarr[i]!=0){
            count+=1;
            printf("%d\t",changedarr[i]);   
        }
}
    printf("\n");
}
0
ashim888

Вставьте все элементы в binary tree the disregards duplicates - O(nlog(n)). Затем извлеките их все обратно в массив, выполнив обход - O(n). Я предполагаю, что вам не нужно сохранение порядка.

0
Ashwin

Во-первых, вы должны создать массив check[n], где n - это количество элементов массива, которое вы хотите сделать без дубликатов, и установить значение каждого элемента (проверочного массива) равным 1. Используя цикл for, переберите массив с помощью дубликаты, скажем, его имя arr, и в цикле for напишите это:

{
    if (check[arr[i]] != 1) {
        arr[i] = 0;
    }
    else {
        check[arr[i]] = 0;
    }
}

При этом вы устанавливаете каждый дубликат равным нулю. Поэтому остается только пройти через массив arr и вывести все, что не равно нулю. Порядок остается, и он занимает линейное время (3 * n).

0
Grabenfly