Algoritmo: modo efficiente per rimuovere interi duplicati da un array


92

Ho avuto questo problema da un'intervista con Microsoft.

Dato un array di numeri interi casuali, scrivi un algoritmo in C che rimuova i numeri duplicati e restituisca i numeri univoci nell'array originale.

Ad esempio Ingresso: {4, 8, 4, 1, 1, 2, 9} Uscita:{4, 8, 1, 2, 9, ?, ?}

Un avvertimento è che l'algoritmo previsto non dovrebbe richiedere prima l'ordinamento dell'array. E quando un elemento è stato rimosso, anche i seguenti elementi devono essere spostati in avanti. Ad ogni modo, il valore degli elementi alla fine della matrice in cui gli elementi sono stati spostati in avanti sono trascurabili.

Aggiornamento: il risultato deve essere restituito nell'array originale e la struttura dei dati di supporto (ad es. Tabella hash) non deve essere utilizzata. Tuttavia, immagino che la conservazione dell'ordine non sia necessaria.

Update2: Per coloro che si chiedono perché questi vincoli poco pratici, questa era una domanda da intervista e tutti questi vincoli vengono discussi durante il processo di pensiero per vedere come posso trovare idee diverse.


4
Devi preservare l'ordine dei numeri univoci?
Douglas Leeder,

1
Il risultato deve essere restituito nell'array originale?
Douglas Leeder

1
Ho aggiornato la domanda. Il risultato dovrebbe essere restituito nell'array originale. Tuttavia, l'ordine della sequenza non ha importanza.
ejel

3
È piuttosto fastidioso quando qualcuno utilizza la risposta alla domanda e ad altre risposte. Sii paziente, le persone ci arriveranno.
GManNickG

2
Perché non è consentita una tabella hash? Questa restrizione non ha senso.
RBarryYoung

Risposte:


19

Che ne dite di:

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++;
            }
        }
    }
}

Dovrebbe essere O (n ^ 2) o inferiore.


3
Questa è la soluzione semplice ed è più che probabile ciò che la domanda dell'intervista sta cercando.
Kirk Broadhurst,

7
Potrebbero anche controllare per vedere che non soffri di indulgere in un'ottimizzazione prematura a meno che non ti abbiano dato anche vincoli di runtime! :-)
Trevor Tippins

16
Lol, anche se è decisamente più veloce ordinare l'array e lavorare su quello ordinato. L'ordinamento dovrebbe essere fornito da un'API e non è un'ottimizzazione prematura.
ziggystar

2
Non dovrebbe essere while (current <= end) invece di while (current <end)?
Shail

2
Perché è stata accettata come la risposta giusta? Se la conservazione dell'ordine non è necessaria, non è meglio usare solo merge sort O (nlogn) e quindi rimuovere gli elementi ripetuti in O (n) ... complessità totale - O (nlogn) che è molto meglio di questa soluzione.
Pawan

136

Una soluzione suggerita dalla mia ragazza è una variazione del merge sort. L'unica modifica è che durante la fase di unione, ignorare semplicemente i valori duplicati. Questa soluzione sarebbe anche O (n log n). In questo approccio, l'ordinamento / rimozione della duplicazione vengono combinati insieme. Tuttavia, non sono sicuro che questo faccia la differenza.


8
Ottimo suggerimento, ma avrai bisogno di un po 'di contabilità per tenere traccia della fine di ogni output di unione. In realtà l'ho fatto una volta, e sì, eliminare i duplicati mentre si uniscono lo rende molto più veloce.
Mark Ransom

2
Non è chiaro se O (N / 2) spazio extra conti come la "struttura dati di supporto" vietata nella domanda: non so se la restrizione sia intesa a stipulare O (1) spazio extra, o semplicemente a stabilire che il la risposta non dovrebbe dipendere da una grande implementazione della struttura dati. Forse una fusione standard va bene. Ma in caso contrario, suggerimento: non tentare di scrivere un merge sort sul posto in un'intervista, a meno che tu non sappia veramente cosa stai facendo.
Steve Jessop

Grande idea. Ma richiede che i dati rimanenti mantengano l'ordine originale.
Hardy Feng

4
Segue un documento che descrive ciò che la tua ragazza ha suggerito: dc-pubs.dbs.uni-leipzig.de/files/…
Mike B

50

L'ho già pubblicato una volta su SO, ma lo riprodurrò qui perché è piuttosto interessante. Usa l'hashing, costruendo qualcosa come un set di hash sul posto. È garantito che sia O (1) nello spazio ascellare (la ricorsione è una chiamata di coda), ed è tipicamente O (N) complessità temporale. L'algoritmo è il seguente:

  1. Prendi il primo elemento dell'array, questa sarà la sentinella.
  2. Riordina il resto dell'array, per quanto possibile, in modo che ogni elemento si trovi nella posizione corrispondente al suo hash. Al termine di questo passaggio, verranno rilevati i duplicati. Impostali uguali a sentinella.
  3. Sposta tutti gli elementi per cui l'indice è uguale all'hash all'inizio della matrice.
  4. Sposta tutti gli elementi che sono uguali a sentinel, tranne il primo elemento della matrice, alla fine della matrice.
  5. Ciò che rimane tra gli elementi correttamente hash e gli elementi duplicati saranno gli elementi che non è stato possibile posizionare nell'indice corrispondente al loro hash a causa di una collisione. Ricorso per affrontare questi elementi.

Questo può essere dimostrato essere O (N) a condizione che non vi sia alcuno scenario patologico nell'hashing: anche se non ci sono duplicati, circa 2/3 degli elementi verranno eliminati ad ogni ricorsione. Ogni livello di ricorsione è O (n) dove piccolo n è la quantità di elementi rimasti. L'unico problema è che, in pratica, è più lento di un ordinamento rapido quando ci sono pochi duplicati, cioè molte collisioni. Tuttavia, quando ci sono enormi quantità di duplicati, è incredibilmente veloce.

Modifica: nelle attuali implementazioni di D, hash_t è di 32 bit. Tutto ciò che riguarda questo algoritmo presuppone che ci saranno pochissime, se non nessuna, collisioni hash nello spazio completo a 32 bit. Tuttavia, le collisioni possono verificarsi frequentemente nello spazio del modulo. Tuttavia, questa ipotesi sarà con ogni probabilità vera per qualsiasi set di dati di dimensioni ragionevoli. Se la chiave è minore o uguale a 32 bit, può essere il proprio hash, il che significa che una collisione nello spazio completo di 32 bit è impossibile. Se è più grande, semplicemente non ne puoi inserire abbastanza nello spazio degli indirizzi di memoria a 32 bit perché sia ​​un problema. Presumo che hash_t verrà aumentato a 64 bit nelle implementazioni a 64 bit di D, dove i set di dati possono essere più grandi. Inoltre, se questo dovesse mai rivelarsi un problema, si potrebbe cambiare la funzione hash ad ogni livello di ricorsione.

Ecco un'implementazione nel linguaggio di programmazione 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
Risposta estremamente interessante e sottovalutata! Mi piace l'idea di utilizzare l'elemento in posizione 1 come valore sentinella. Se potessi dare un paio di piccoli suggerimenti, sarebbe quello di modificare il passaggio 2 per includere "ogni elemento è nella posizione corrispondente al suo hash modulo la dimensione dell'array ", e forse chiarire che i duplicati da impostare sulla sentinella sono i elementi che hanno lo stesso valore (in contrasto con lo stesso hash o la stessa dimensione dell'array modulo hash).
j_random_hacker

20

Un'implementazione più efficiente

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];
}

In questa implementazione non è necessario ordinare l'array. Inoltre, se viene trovato un elemento duplicato, non è necessario spostare tutti gli elementi successivi di una posizione.

L'output di questo codice è array [] con dimensione NewLength

Qui stiamo iniziando dal 2 ° elemento in array e confrontandolo con tutti gli elementi in array fino a questo array. Stiamo tenendo una variabile di indice extra 'NewLength' per modificare l'array di input. La variabile NewLength è inizializzata a 0.

L'elemento nell'array [1] verrà confrontato con l'array [0]. Se sono diversi, il valore in array [NewLength] verrà modificato con array [1] e incrementerà NewLength. Se sono uguali, NewLength non verrà modificato.

Quindi, se abbiamo un array [1 2 1 3 1], allora

Nel primo passaggio del ciclo 'j', array [1] (2) verrà confrontato con array0, quindi 2 verrà scritto in array [NewLength] = array [1] quindi array sarà [1 2] poiché NewLength = 2

Nel secondo passaggio del ciclo 'j', array [2] (1) verrà confrontato con array0 e array1. In questo caso, poiché array [2] (1) e array0 sono lo stesso ciclo, qui si interromperà. quindi array sarà [1 2] poiché NewLength = 2

e così via


3
Ben fatto. Ho un suggerimento per migliorare. Secondo ciclo nidificato può essere cambiato in for (j = 0; j <NewLength; j ++) e l'ultimo, se la verifica può essere modificato in se (j == NewLength)
Vadakkumpadath

È stato un ottimo suggerimento. Ho aggiornato il codice in base al tuo commento
Byju

Fallisce almeno se abbiamo gli stessi valori nell'array {1,1,1,1,1,1}. Codice inutile.
Yuriy Chernyshov,

Ebbene qual è la complessità di questo, non è anche O (n ^ 2)?
JavaSa

1
Tanti voti positivi, ma questo non è efficiente: è O (n ^ 2) quando ci sono pochi duplicati.
Paul Hankin

19

Se stai cercando la notazione O superiore, ordinare l'array con un ordinamento O (n log n) e quindi eseguire un attraversamento O (n) potrebbe essere la strada migliore. Senza l'ordinamento, stai guardando O (n ^ 2).

Modifica: se stai solo facendo numeri interi, puoi anche eseguire l'ordinamento digitale per ottenere O (n).


La risposta di Jeff B è semplicemente O (n). Hash-set e hash-dizionari sono le ginocchia delle api.
ChrisW

3
ChrisW: gli hash set / dizionari sono solo O (1) se non si assume alcuna collisione. (Non sto dicendo che non li userei per questo problema - probabilmente lo farei - è solo un errore affermare che sono veramente O (1).)
Laurence Gonsalves

2
In realtà, poiché conosci in anticipo la dimensione dell'array, puoi garantire O (1). Quindi puoi bilanciare le collisioni con la quantità di memoria aggiuntiva che usi.
Vitali

Potresti voler ripensare a quel voto negativo: le nuove condizioni del problema rendono la soluzione di Jeff B non valida.
Mark Ransom

3
Potresti voler approfondire l '"attraversamento", poiché un metodo di cancellazione ingenuo potrebbe risultare in O (n ^ 2) per un gran numero di duplicati.
Mark Ransom

11

1. Usando O (1) spazio extra, in tempo O (n log n)

Questo è possibile, ad esempio:

  • prima esegui un ordinamento sul posto O (n log n)
  • quindi scorrere l'elenco una volta, scrivendo la prima istanza di ogni indietro all'inizio dell'elenco

Credo che il partner di ejel abbia ragione sul fatto che il modo migliore per farlo sarebbe un ordinamento sul posto con un passaggio di unione semplificato, e che questo è probabilmente l'intento della domanda, se tu fossi ad es. scrivere una nuova funzione di libreria per farlo nel modo più efficiente possibile senza alcuna possibilità di migliorare gli input, e in alcuni casi sarebbe utile farlo senza una tabella hash, a seconda del tipo di input. Ma in realtà non l'ho controllato.

2. Utilizzando O (lotti) spazio extra, in O (n) tempo

  • dichiarare un array zero abbastanza grande da contenere tutti i numeri interi
  • attraversare l'array una volta
  • imposta l'elemento della matrice corrispondente su 1 per ogni numero intero.
  • Se era già 1, salta il numero intero.

Funziona solo se sussistono diversi presupposti discutibili:

  • è possibile azzerare la memoria in modo economico, oppure la dimensione degli int è piccola rispetto al numero di essi
  • sei felice di chiedere al tuo sistema operativo 256 ^ sizepof (int) memory
  • e lo memorizzerà per te in modo davvero molto efficiente se è gigantesco

È una cattiva risposta, ma se hai MOLTI elementi di input, ma sono tutti interi a 8 bit (o forse anche interi a 16 bit) potrebbe essere il modo migliore.

3. O (poco) -ish spazio extra, O (n) -ish tempo

Come # 2, ma usa una tabella hash.

4. Il modo chiaro

Se il numero di elementi è piccolo, scrivere un algoritmo appropriato non è utile se altro codice è più veloce da scrivere e più veloce da leggere.

Per esempio. Esamina l'array per ogni elemento univoco (cioè il primo elemento, il secondo elemento (i duplicati del primo sono stati rimossi) ecc.) Rimuovendo tutti gli elementi identici. O (1) spazio extra, O (n ^ 2) tempo.

Per esempio. Usa le funzioni di libreria che lo fanno. l'efficienza dipende da quello che hai facilmente a disposizione.


7

Bene, l'implementazione di base è abbastanza semplice. Passa attraverso tutti gli elementi, controlla se ci sono duplicati in quelli rimanenti e sposta il resto su di essi.

È terribilmente inefficiente e potresti accelerarlo con un array di supporto per l'output o gli alberi di ordinamento / binari, ma questo non sembra essere consentito.


1
OTOH, il codice aggiuntivo richiesto per implementare un albero di ordinamento potrebbe essere meno efficiente (in termini di memoria) rispetto alla soluzione semplice, ed è probabilmente meno efficiente in fase di esecuzione per array piccoli (diciamo meno di 100 elementi).
TMN

6

Se puoi usare C ++, una chiamata a std::sortseguita da una chiamata a std::uniqueti darà la risposta. La complessità temporale è O (N log N) per l'ordinamento e O (N) per l'attraversamento unico.

E se C ++ è fuori dal tavolo non c'è niente che impedisce che questi stessi algoritmi vengano scritti in C.


"Un avvertimento è che l'algoritmo previsto non dovrebbe richiedere prima l'ordinamento dell'array."
sbi

2
Non dice che non puoi ordinare l'array una volta ottenuto ... Senza usare O (N) l'ordinamento della memoria esterna è l'unico modo per farlo in O (N log N) o migliore.
Greg Rogers,

Ai fini del problema, le utilità di libreria standard non dovrebbero essere utilizzate. Per quanto riguarda l'ordinamento, tuttavia, più ci penso, più sono incerto se sia ok o meno.
ejel

1
Penso che le risposte che si riferiscono alle funzioni standard C ++ e C ++ siano utili, anche se non rispondono alla domanda originale, poiché forniscono una risposta più completa alle persone che trovano questa domanda in seguito.
Douglas Leeder

6

Potresti farlo in una singola traversata, se sei disposto a sacrificare la memoria. Puoi semplicemente calcolare se hai visto o meno un numero intero in un array hash / associativo. Se hai già visto un numero, rimuovilo mentre procedi, o meglio ancora, sposta i numeri che non hai visto in un nuovo array, evitando qualsiasi spostamento nell'array originale.

In Perl:

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

Non è chiaro se la risposta debba essere nell'array originale.
Douglas Leeder,

Per fare ciò senza richiedere un nuovo array, potresti semplicemente sostituire il duplicato con un elemento estratto dalla fine dell'array e ripetere il ciclo corrente, poiché il problema non specifica che l'ordine è importante. Ciò richiede un controllo dei limiti extra, ma è molto fattibile.
Jeff B

6
Questa è stata una buona idea, fino a quando la domanda non è stata modificata. La tua idea di hashtable è apparentemente contro le regole.
WCWedin

14
Non capisco perché questa risposta venga votata di più. È scritto in perl e utilizza funzionalità vitali non disponibili in C, come la domanda chiede.
LiraNuna

5
la domanda chiedeva codice c, non perl. usando perl si ottengono tabelle hash e "push" gratuitamente. Se potessi farlo in scala chiameresti semplicemente input.removeDuplicates, ma dubito che sarebbe stato accettabile per gli intervistatori :)
Peter Recore

5

Il valore di ritorno della funzione dovrebbe essere il numero di elementi univoci e sono tutti memorizzati nella parte anteriore della matrice. Senza queste informazioni aggiuntive, non saprai nemmeno se ci sono stati duplicati.

Ogni iterazione del ciclo esterno elabora un elemento dell'array. Se è univoco, resta in primo piano nell'array e se è un duplicato viene sovrascritto dall'ultimo elemento non elaborato dell'array. Questa soluzione viene eseguita in tempo 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

Ecco una versione 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;       
    }

Non riesce almeno con i successivi input: {1,1,1,1,1,1,1} {0,0,0,0,0,1,1,1,1,1,1}
Yuriy Chernyshov,

3

Ecco la mia soluzione.

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

Un array dovrebbe ovviamente essere "attraversato" da destra a sinistra per evitare la copia inutile dei valori avanti e indietro.

Se disponi di memoria illimitata, puoi allocare un array di bit per sizeof(type-of-element-in-array) / 8byte in modo che ogni bit indichi se hai già incontrato il valore corrispondente o meno.

Se non lo fai, non riesco a pensare a niente di meglio che attraversare un array e confrontare ogni valore con i valori che lo seguono e quindi se viene trovato un duplicato, rimuovi del tutto questi valori. Questo è da qualche parte vicino a O (n ^ 2) (o O ((n ^ 2-n) / 2) ).

IBM ha un articolo su un argomento piuttosto vicino.


In effetti, un passaggio O (n) per trovare l'elemento più grande non aumenterebbe il costo O () complessivo.
Douglas Leeder,

2

Vediamo:

  • O (N) passare per trovare allocazione min / max
  • matrice di bit per trovato
  • O (N) passa lo scambio di duplicati alla fine.

Dato che sono solo numeri interi, per semplicità potresti assumere 32 bit e non preoccuparti di cercare min / max: 2 ^ 32 bit sono "solo" 512 MB, quindi trovare i limiti è solo un uso della memoria e un'ottimizzazione del tempo O (1) (concesso, una notevole ottimizzazione nel caso dell'esempio fornito). E se sono a 64 bit, è irrilevante poiché non sai che il minimo e il massimo non saranno più distanti del numero di bit di memoria che hai.
Steve Jessop

Teoria a parte, l'allocazione di 512 MB non richiederebbe più tempo che trovare il minimo / massimo?
LiraNuna

Dipende dalla quantità di dati presenti e da quali sono i valori min / max. Se stai guardando più di 512 MB di input, allora molto probabilmente è più veloce evitare quel passaggio O (N) extra. Ovviamente se stai guardando così tanto input, è meno probabile che tu abbia 512 MB di spazio libero. Nei casi in cui min / max sono vicini a 0 / INT_MAX, anche l'ottimizzazione non aiuta. Sto solo dicendo che sebbene il primo passaggio aiuti ovviamente per piccoli numeri, non può evitare il fatto che questo algoritmo utilizza i bit UINT_MAX nel caso peggiore, quindi è necessario pianificare tale limitazione.
Steve Jessop

Potresti avere ragione - in ogni caso, chiarire la domanda significa che l'uso di un array di bit è fuori uso. Lascio questa risposta nel caso in cui qualcuno si presenti più tardi senza i vincoli e desideri visualizzare tutte le possibili risposte.
Douglas Leeder

2

Questo può essere fatto in un solo passaggio con un algoritmo O (N log N) e senza spazio di archiviazione aggiuntivo.

Procedi dall'elemento a[1]a a[N]. In ogni fase i, tutti gli elementi a sinistra di a[i]comprendono un mucchio ordinato di elementi a[0]attraverso a[j]. Nel frattempo, un secondo indice j, inizialmente 0, tiene traccia della dimensione dell'heap.

Esaminalo a[i]e inseriscilo nell'heap, che ora occupa gli elementi a[0]per a[j+1]. Quando l'elemento viene inserito, se a[k]si incontra un elemento duplicato con lo stesso valore, non inserirlo a[i]nell'heap (ovvero scartarlo); altrimenti inserirla nel cumulo, che ora cresce da un elemento e comprende ora a[0]a a[j+1], e l'incremento j.

Continua in questo modo, incrementando ifino a quando tutti gli elementi dell'array non sono stati esaminati e inseriti nell'heap, che finisce per occupare a[0]a a[j]. jè l'indice dell'ultimo elemento dell'heap e l'heap contiene solo valori di elemento univoci.

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

Guardando l'esempio, questo non è esattamente ciò che è stato richiesto poiché l'array risultante conserva l'ordine originale degli elementi. Ma se questo requisito è rilassato, l'algoritmo sopra dovrebbe fare il trucco.


1

In Java lo risolverei in questo modo. Non so come scrivere questo in 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;
         }
      }
   }

Se sovrascrivi i duplicati che trovi con il valore alla fine dell'array, puoi evitare lo spostamento dell'intero array nel tuo ciclo interno for (). Questo ti porterà a O (n ^ 2) da O (n ^ 3). La mia implementazione C sta fluttuando qui da qualche parte ...
mocj

Ho pensato che cambiare fosse parte del requisito, ma ovviamente hai ragione.
Dominik

1
@mocj: mi piace la tua soluzione, sembra molto elegante. Ma penso che non funzioni se gli ultimi due elementi sono uguali, perché smetti di controllare l'uguaglianza uno prima dell'ultimo. (commenti qui perché hanno troppa reputazione per commentare altrove :()
Dominik

Hai ragione tranne per il fatto che il problema originale afferma che i valori alla fine dell'array sono trascurabili. Poiché non si restituisce la lunghezza dell'array modificato, la distinzione tra l'ultimo valore e il penultimo non è importante quando i due valori sono uguali. Dove interpreta la fine dell'array restituito da parte del
chiamante

1

Che ne dici di quanto segue?

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

Provo a dichiarare un array temporaneo e inserire gli elementi in quello prima di copiare tutto nell'array originale.


1

Dopo aver esaminato il problema, ecco il mio metodo delphi, che potrebbe aiutare

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

Il seguente esempio dovrebbe risolvere il tuo problema:

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] dovrebbe generare ArrayIndexOutOfBoundsException per l'ultimo elemento?
Sathesh

@Sathesh No. A causa di "<arr.length-1"
GabrielBB

1

Questa è la soluzione ingenua (N * (N-1) / 2). Utilizza uno spazio aggiuntivo costante e mantiene l'ordine originale. È simile alla soluzione di @Byju, ma non utilizza if(){}blocchi. Inoltre evita di copiare un elemento su se stesso.

#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

Questa operazione può essere eseguita in un unico passaggio, nel tempo O (N) nel numero di numeri interi nell'elenco di input e nell'archiviazione O (N) nel numero di interi univoci.

Scorri l'elenco da davanti a dietro, con due puntatori "dst" e "src" inizializzati sul primo elemento. Inizia con una tabella hash vuota di "numeri interi visti". Se il numero intero in src non è presente nell'hash, scriverlo nello slot in dst e incrementare dst. Aggiungi il numero intero in src all'hash, quindi incrementa src. Ripeti finché src non supera la fine dell'elenco di input.


2
Nella modifica alla domanda originale, le tabelle hash non sono consentite. Il tuo approccio a due puntatori è un bel modo per compattare l'output una volta identificati i duplicati, però.
Mark Ransom

0

Inserisci tutti gli elementi in a binary tree the disregards duplicates- O(nlog(n)). Quindi estrarli tutti di nuovo nell'array eseguendo un attraversamento - O(n). Presumo che tu non abbia bisogno della conservazione dell'ordine.


0

Usa il filtro bloom per l'hashing. Ciò ridurrà notevolmente il sovraccarico della memoria.


ti interessa elaborare o fornire un riferimento?
dldnh

0

In 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]);
    }

output: {1, 2, 3, 4, 6, 7, 8, 9, 10}

spero che questo possa aiutare


1
Prova questo con l'inputarrayInteger = {100,10,1};
Blastfurnace


0

Per prima cosa, dovresti creare un array check[n]dove n è il numero di elementi dell'array che vuoi rendere privi di duplicati e impostare il valore di ogni elemento (dell'array di controllo) uguale a 1. Usando un ciclo for attraversare l'array con il duplicati, diciamo che il suo nome è arr, e nel ciclo for scrivi questo:

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

Con ciò, imposti ogni duplicato uguale a zero. Quindi l'unica cosa che resta da fare è attraversare l' arrarray e stampare tutto ciò che non è uguale a zero. L'ordine rimane e richiede tempo lineare (3 * n).


La domanda non consente l'utilizzo di una struttura dati aggiuntiva.
ejel

0

Dato un array di n elementi, scrivi un algoritmo per rimuovere tutti i duplicati dall'array nel tempo 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.

In altri elementi viene mantenuto nell'array di output utilizzando la 'chiave'. Considera che la chiave è di lunghezza O (n), il tempo impiegato per eseguire l'ordinamento sulla chiave e il valore è O (nlogn). Quindi il tempo impiegato per eliminare tutti i duplicati dall'array è O (nlogn).


Con tutti i glifi in grassetto, cosa ne avete fatto helper data structure (e.g. hashtable) should not be used?
barba grigia

Non necessariamente necessario. Li ho solo evidenziati allo scopo di capire.
Sharief Muzammil

0

questo è quello che ho, anche se sbaglia l'ordine che possiamo ordinare in ordine crescente o decrescente per risolverlo.

#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

Sarebbe bello se tu avessi una buona DataStructure che potrebbe dire rapidamente se contiene un numero intero. Forse un albero di qualche tipo.

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.