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


92

Цю проблему я отримав з інтерв’ю з Microsoft.

Враховуючи масив випадкових цілих чисел, напишіть алгоритм на мові C, який видаляє дубльовані числа та повертає унікальні числа у вихідному масиві.

Наприклад, вхід: {4, 8, 4, 1, 1, 2, 9} вихід:{4, 8, 1, 2, 9, ?, ?}

Одне застереження полягає в тому, що очікуваний алгоритм не повинен вимагати спочатку сортування масиву. А коли елемент вилучено, наступні елементи також слід перемістити вперед. У будь-якому випадку, значення елементів у хвості масиву, де елементи були зміщені вперед, є незначним.

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

Оновлення 2: Для тих, хто задається питанням, чому ці непрактичні обмеження, це було питання для співбесіди, і всі ці обмеження обговорюються під час процесу мислення, щоб побачити, як я можу висловити різні ідеї.


4
Чи потрібно зберігати порядок унікальних чисел?
Дуглас Лідер,

1
Чи потрібно повертати результат у вихідному масиві?
Дуглас Лідер,

1
Я оновив запитання. Результат повинен бути повернутий у вихідному масиві. Однак порядок послідовності значення не має.
ejel

3
Досить прикро, коли хтось сутенер відповідає на питання та інші відповіді. Тільки терпіть, люди туди потраплять.
GManNickG

2
Чому хешрейт не дозволяється? Це обмеження не має сенсу.
RBarryYoung

Відповіді:


19

Як щодо:

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) або менше.


3
Це просте рішення і, швидше за все, те, що шукає питання співбесіди.
Кірк Бродхерст,

7
Можливо, вони навіть перевіряють, чи не страждаєте ви від передчасної оптимізації, якщо вони теж не обмежили вас часом роботи! :-)
Тревор Тіппінс

16
Лол, хоча, безумовно, швидше сортувати масив і працювати над відсортованим. Сортування повинно забезпечуватися API і не передбачає передчасної оптимізації.
ziggystar

2
Чи не повинно бути while (current <= end) замість while (current <end)?
Shail

2
Чому це було прийнято як правильну відповідь? Якщо збереження замовлення не потрібно, то чи не краще просто використовувати злиття сорту O (nlogn), а потім видалити повторювані елементи в O (n) ... загальна складність - O (nlogn), що набагато краще, ніж це рішення.
Паван

136

Рішення, запропоноване моєю дівчиною, - це різновид сортування об’єднань. Єдина модифікація полягає в тому, що на етапі злиття просто ігноруйте повторювані значення. Це рішення було б також O (n log n). У цьому підході сортування / видалення копій поєднуються разом. Однак я не впевнений, чи це щось змінює.


8
Чудова порада, але вам знадобиться деяка бухгалтерія, щоб відстежувати кінець кожного результату злиття. Я насправді зробив це один раз, і так, усунення дублікатів під час об’єднання робить це набагато швидшим.
Марк Ренсом,

2
Незрозуміло, чи зайвий простір O (N / 2) вважається забороненою у питанні "допоміжною структурою даних" - я не знаю, чи призначене обмеження передбачає додатковий простір O (1), чи просто передбачає, що відповідь не повинна залежати від реалізації великої структури даних. Можливо, стандартне злиття - це нормально. Але якщо ні, головна порада: не намагайтеся писати злиття на місці в інтерв’ю, якщо ви насправді не знаєте, що робите.
Steve Jessop

Чудова ідея. Але це вимагає, щоб решта даних зберігала оригінальне замовлення.
Харді Фенг

4
Наступний документ, в якому описується пропозиція вашої дівчини: dc-pubs.dbs.uni-leipzig.de/files/…
Mike B,

50

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

  1. Візьміть перший елемент масиву, це буде сторожовий.
  2. Впорядкуйте решту масиву, наскільки це можливо, таким чином, щоб кожен елемент знаходився в положенні, що відповідає його хешу. Після завершення цього кроку будуть виявлені дублікати. Встановіть їх рівними сторожовим.
  3. Перемістіть усі елементи, для яких індекс дорівнює хешу, на початок масиву.
  4. Перемістіть усі елементи, які дорівнюють вартовому, крім першого елемента масиву, у кінець масиву.
  5. Що залишиться між правильно хешованими елементами та повторюваними елементами, це елементи, які не можуть бути розміщені в індексі, що відповідає їхньому хешу через зіткнення. Повторіть справу з цими елементами.

Це можна показати як O (N) за умови відсутності патологічного сценарію хешування: Навіть якщо дублікатів немає, приблизно 2/3 елементів буде вилучено під час кожної рекурсії. Кожен рівень рекурсії - O (n), де мале 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);
}

1
Надзвичайно крута, недооцінена відповідь! Мені подобається ідея використання елемента в позиції 1 як вартового значення. Якби я міг зробити кілька невеликих пропозицій, було б змінити крок 2, включивши "кожен елемент знаходиться в положенні, що відповідає його хешу за модулем розміру масиву ", і, можливо, пояснити, що дублікати, які слід встановити для сторожового, є елементи, що мають однакове значення (на відміну від одного і того ж хешу або одного і того ж розміру масиву хешу).
j_random_hacker

20

Ще одна ефективна реалізація

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] = array [1], тому масив буде [1 2], оскільки NewLength = 2

У другому проході циклу 'j' масив [2] (1) буде порівняно з масивом0 та масивом1. Оскільки масив [2] (1) і масив0 є однаковим циклом, тут буде порушено. тож масив буде [1 2], оскільки NewLength = 2

і так далі


3
Хороший. У мене є пропозиція вдосконалити. Другий вкладений цикл можна змінити на for (j = 0; j <NewLength; j ++) і останній, якщо перевірку можна змінити на if (j == NewLength)
Vadakkumpadath

Це було чудовою пропозицією. Я оновив код на основі коментаря ур
Byju

Помилка, принаймні, якщо ми маємо однакові значення в масиві {1,1,1,1,1,1}. Даремний код.
Юрій Чернишов

Ну в чому ж складність цього, чи не також це O (n ^ 2)?
JavaSa

1
Так багато голосів, але це неефективно: це O (n ^ 2), коли дублікатів мало.
Пол Хенкін

19

Якщо ви шукаєте чудову O-нотацію, то найкращим маршрутом може бути сортування масиву за допомогою сортування O (n log n), а потім обхід O (n). Не сортуючи, ви дивитесь на O (n ^ 2).

Редагувати: якщо ви просто робите цілі числа, то ви також можете зробити сортування за радіусом, щоб отримати O (n).


Відповідь Джеффа Б - просто O (n). Хеш-набори та хеш-словники - це коліна бджіл.
ChrisW

3
ChrisW: хеш-набори / словники є лише O (1), якщо ви не припускаєте зіткнень. (Я не кажу, що не використовував би їх для цієї проблеми - я, мабуть, використав би - це просто помилка стверджувати, що вони справді О (1).)
Лоуренс Гонсалвес,

2
Насправді, оскільки ви вже знаєте розмір масиву, ви можете гарантувати O (1). Тоді ви зможете обміняти зіткнення та обсяг додаткової пам'яті, яку ви використовуєте.
Віталій

Можливо, ви захочете переглянути це голосування проти - нещодавно розміщені умови проблеми роблять рішення Джеффа Б недійсним.
Марк Ренсом

3
Можливо, ви захочете детальніше розповісти про "обхід", оскільки метод наївного стирання може призвести до O (n ^ 2) для великої кількості дублікатів.
Марк Ренсом

11

1. Використовуючи додатковий простір O (1), за час O (n log n)

Це можливо, наприклад:

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

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

2. Використовуючи додатковий простір O (багато), за час O (n)

  • оголосити нульовий масив, достатньо великий, щоб вмістити всі цілі числа
  • пройдіться по масиву один раз
  • встановіть для відповідного елемента масиву значення 1 для кожного цілого числа.
  • Якщо це вже було 1, пропустіть це ціле число.

Це працює, лише якщо виконується кілька сумнівних припущень:

  • можливо обнулити пам’ять дешево, або розмір дюймів невеликий порівняно з їх кількістю
  • Ви із задоволенням попросите у своєї ОС пам’ять 256 ^ sizepof (int)
  • і він кешує його для вас дійсно ефективно, якщо він гігантський

Це погана відповідь, але якщо у вас БАГАТО вхідних елементів, але всі вони 8-бітові цілі числа (або, можливо, навіть 16-бітові цілі числа), це може бути найкращим способом.

3. O (мало) -іш зайвий простір, O (n) -іш час

Як №2, але використовуйте хеш-таблицю.

4. Ясний шлях

Якщо кількість елементів невелика, написання відповідного алгоритму не корисно, якщо інший код швидше пишеться і швидше читається.

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

Напр. Використовуйте функції бібліотеки, які роблять це. ефективність залежить від того, який у вас є легко доступний.


7

Ну, це базова реалізація досить проста. Перегляньте всі елементи, перевірте, чи немає дублікатів в решті, а решту перемістіть по них.

Це страшно неефективно, і ви могли б пришвидшити його допоміжним масивом для виводу чи сортування / двійкових дерев, але це, схоже, не дозволено.


1
OTOH, додатковий код, необхідний для реалізації дерева сортування, може бути менш ефективним (пам’яті), ніж просте рішення, і, ймовірно, менш ефективний під час роботи для малих (скажімо, менше 100 елементів) масивів.
TMN

6

Якщо вам дозволено використовувати С ++, відповідь дасть дзвінок, за std::sortяким слідує дзвінок std::unique. Складність часу становить O (N log N) для сортування та O (N) для унікального обходу.

І якщо C ++ не стоїть на столі, немає нічого, що заважає цим самим алгоритмам записуватись на C.


"Одне застереження полягає в тому, що очікуваний алгоритм не повинен вимагати спочатку сортування масиву."
sbi

2
Це не означає, що ви не можете сортувати масив, як тільки його отримаєте ... Без використання O (N) сортування зовнішньої пам'яті - це єдиний спосіб зробити це в O (N log N) або краще.
Грег Роджерс

З метою вирішення проблеми не слід використовувати стандартні утиліти бібліотеки. Щодо сортування, однак, чим більше я про це думаю, тим більше не впевнений, чи це нормально чи ні.
ejel

1
Я думаю, що відповіді, що стосуються стандартних функцій C ++ та C ++, є корисними, навіть якщо вони не відповідають на вихідне питання, оскільки вони дають більш округлу відповідь людям, які знайдуть це питання пізніше.
Дуглас Лідер,

6

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

У Perl:

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        push @newary, $i;
    }
}

Незрозуміло, чи відповідь має бути у вихідному масиві.
Дуглас Лідер,

Щоб зробити це, не вимагаючи нового масиву, ви можете просто замінити дублікат елементом, що вискочив з кінця масиву, і повторити поточний цикл, оскільки проблема не вказує, що порядок має значення. Це вимагає додаткових перевірок меж, але це дуже можливо.
Jeff B

6
Це була гарна ідея, доки запитання не було відредаговано. Ваша ідея хеш-таблиці суперечить правилам.
WCWedin

14
Я не розумію, чому за цю відповідь голосують найбільше. Він написаний на perl і використовує життєво важливі функції, недоступні на мові C, як задає питання.
LiraNuna

5
питання, яке задається кодом c, а не perl. використання perl дає вам хештеги і "натискання" безкоштовно. Якби я міг зробити це в масштабі, ви б просто зателефонували input.removeDuplicates, але я сумніваюся, що це було б прийнятно для інтерв'юерів :)
Peter Recore

5

Повернене значення функції повинно бути кількістю унікальних елементів, і всі вони зберігаються в передній частині масиву. Без цієї додаткової інформації ви навіть не дізнаєтесь, чи були дублікати.

Кожна ітерація зовнішнього циклу обробляє один елемент масиву. Якщо він унікальний, він залишається в передній частині масиву, а якщо він є дублікатом, він перезаписується останнім необробленим елементом масиву. Це рішення працює за час 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;
}

4

Ось версія 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;       
    }

Помилка принаймні з наступними входами: {1,1,1,1,1,1,1} {0,0,0,0,0,1,1,1,1,1,1}
Юрій Чернишов

3

Ось моє рішення.

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

Очевидно, що масив слід "обводити" справа наліво, щоб уникнути непотрібного копіювання значень туди-сюди.

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

Якщо ви цього не зробите, я не можу придумати нічого кращого, ніж обхід масиву та порівняння кожного значення зі значеннями, які слідують за ним, а потім, якщо знайдено дублікат, видаліть ці значення взагалі. Це десь близько O (n ^ 2) (або O ((n ^ 2-n) / 2) ).

IBM має статтю про досить близьку тему.


Дійсно - пропуск O (n) для пошуку найбільшого елемента не збільшить загальної вартості O ().
Дуглас Лідер,

2

Подивимось:

  • Перехід O (N) для пошуку мінімального / максимального розподілу
  • бітовий масив для знайденого
  • O (N) передати дублікати заміни до кінця.

Враховуючи, що це лише цілі числа, для простоти ви можете взяти 32 біт і не турбуватися про пошук мін / макс: 2 ^ 32 біта - це "лише" 512 МБ, тому пошук меж - це просто використання пам'яті та оптимізація часу O (1) (надано, неабияку оптимізацію у випадку наведеного прикладу). І якщо вони 64-бітні, це не має значення, оскільки ви не знаєте, що min і max не будуть далі, ніж кількість бітів пам'яті, яку ви маєте.
Steve Jessop

Окрім теорії, чи не виділення 512 Мб займе більше часу, ніж пошук мінімальної / максимальної?
LiraNuna

Залежить від кількості даних та мінімальної / максимальної величини. Якщо ви шукаєте більше 512 МБ вводу, то цілком можливо швидше уникнути зайвого проходження O (N). Звичайно, якщо ви дивитесь на стільки вхідних даних, то менш імовірно, що у вас є 512 МБ. У випадках, коли min / max близькі до 0 / INT_MAX, оптимізація теж не допомагає. Я просто кажу, що, хоча перший крок, очевидно, допомагає для малих чисел, він не може уникнути того, що цей алгоритм використовує біти UINT_MAX в гіршому випадку, тому вам потрібно спланувати це обмеження.
Стів Джессоп,

Ви цілком можете мати рацію - у будь-якому випадку з’ясування питання означає, що використання бітового масиву вимкнено. Я залишу цю відповідь на випадок, якщо хтось прийде згодом без обмежень і захоче переглянути всі можливі відповіді.
Дуглас Лідер,

2

Це можна зробити за один прохід за алгоритмом 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

На 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;
         }
      }
   }

Якщо ви перезапишете знайдені дублікати зі значенням у кінці масиву, ви зможете уникнути зміщення всього масиву у вашому внутрішньому циклі for (). Це приведе вас до O (n ^ 2) з O (n ^ 3). Моя реалізація C десь плаває тут ...
mocj

Я думав, перехід був частиною вимоги, але ти, звичайно, маєш рацію.
Домінік

1
@mocj: Мені подобається ваше рішення, виглядає дуже елегантно. Але я думаю, що це не спрацює, якщо останні два елементи рівні, тому що ви перестаєте перевіряти рівність один перед останнім. (Comenting тут , тому що занадто дивитися репутацію коментувати ніде :()
Dominik

Ви маєте рацію, за винятком того, що в оригінальній проблемі зазначено, що значення в кінці масиву незначні. Оскільки ви не повертаєте довжину модифікованого масиву, різниця між останнім і останнім значенням не є важливою, коли ці два значення рівні. Де виклик інтерпретує кінець поверненого масиву як
mocj

1

Як щодо наступного?

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

Ознайомившись із проблемою, ось мій спосіб дельфі, який може допомогти

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

Наступний приклад повинен вирішити вашу проблему:

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

t=[]

output = filter(check_dump, input)

print(output)
True

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

arr [i + 1] повинен викинути ArrayIndexOutOfBoundsException для останнього елемента?
Сатеш

@Sathesh No. Через "<arr.length-1"
GabrielBB,

1

Це наївне рішення (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;
}

0

Це може бути зроблено за один прохід, за час O (N) у кількості цілих чисел у списку введення та O (N) зберігання у кількості унікальних цілих чисел.

Пройдіть по списку спереду назад, з двома покажчиками "dst" і "src", ініціалізованими до першого елемента. Почніть з порожньої хеш-таблиці "цілі числа, побачені". Якщо цілого числа в src немає в хеші, запишіть його в слот на dst і збільште dst. Додайте ціле число в src до хешу, а потім збільште src. Повторюйте, поки src не пройде кінець списку введення.


2
У модифікації вихідного запитання хеш-таблиці не допускаються. Ваш підхід із двома покажчиками - це хороший спосіб ущільнити результат, як тільки ви визначите дублікати.
Марк Ренсом

0

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


0

Для хешування використовуйте фільтр нагрівання. Це значно зменшить накладні витрати на пам’ять.


докладно розробити або надати посилання?
dldnh

0

У 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}

сподіваюся, це допоможе


1
Перевірте це за допомогою вводуarrayInteger = {100,10,1};
Blastfurnace


0

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

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

При цьому ви встановлюєте кожен дублікат рівним нулю. Отже, залишається лише об’їхати arrмасив і надрукувати все, що не дорівнює нулю. Порядок залишається, і це займає лінійний час (3 * n).


Питання не дозволяє використовувати додаткову структуру даних.
ejel

0

Враховуючи масив з 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).


З усіх сміливих гліфів, що ви зробили helper data structure (e.g. hashtable) should not be used?
сіра борода

Не обов’язково. Я просто виділив їх для розуміння.
Sharief Muzammil

0

це те, що я маю, хоча воно не відповідає порядку, який ми можемо сортувати за зростанням або за спаданням, щоб це виправити.

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

-1

Було б круто, якби у вас була хороша структура даних, яка могла швидко визначити, чи містить вона ціле число. Можливо, якесь дерево.

DataStructure elementsSeen = new DataStructure();
int elementsRemoved = 0;
for(int i=0;i<array.Length;i++){
  if(elementsSeen.Contains(array[i])
    elementsRemoved++;
  else
    array[i-elementsRemoved] = array[i];
}
array.Length = array.Length - elementsRemoved;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.