Ecco una semplice O(N)
soluzione che utilizza lo O(N)
spazio. Presumo che stiamo limitando l'elenco di input a numeri non negativi e che vogliamo trovare il primo numero non negativo che non è nell'elenco.
- Trova la lunghezza dell'elenco; diciamo che lo è
N
.
- Alloca un array di
N
booleani, inizializzato a tutti false
.
- Per ogni numero
X
nell'elenco, se X
è minore di N
, imposta l' X'th
elemento della matrice su true
.
- Scansiona l'array partendo da index
0
, cercando il primo elemento che è false
. Se trovi il primo false
in index I
, allora I
è la risposta. Altrimenti (cioè quando tutti gli elementi sono true
) la risposta è N
.
In pratica, l '"array di N
booleani" verrebbe probabilmente codificato come "bitmap" o "bitset" rappresentato come un array byte
o int
. Questo in genere utilizza meno spazio (a seconda del linguaggio di programmazione) e consente di eseguire false
più rapidamente la scansione del primo .
Ecco come / perché funziona l'algoritmo.
Supponiamo che i N
numeri nell'elenco non siano distinti o che uno o più di essi sia maggiore di N
. Ciò significa che deve esserci almeno un numero nell'intervallo 0 .. N - 1
che non è nell'elenco. Quindi il problema di trovare il numero mancante più piccolo deve quindi ridursi al problema di trovare il numero mancante più piccolo minore diN
. Ciò significa che non è necessario tenere traccia dei numeri maggiori o uguali a N
... perché non saranno la risposta.
L'alternativa al paragrafo precedente è che l'elenco è una permutazione dei numeri da 0 .. N - 1
. In questo caso, il passaggio 3 imposta tutti gli elementi dell'array su true
e il passaggio 4 ci dice che il primo numero "mancante" è N
.
La complessità computazionale dell'algoritmo è O(N)
con una costante di proporzionalità relativamente piccola. Esegue due passaggi lineari nell'elenco, o solo un passaggio se si sa che la lunghezza dell'elenco inizia con. Non è necessario rappresentare l'intero elenco in memoria, quindi l'utilizzo asintotico della memoria dell'algoritmo è proprio ciò che è necessario per rappresentare l'array di booleani; cioè O(N)
bit.
(Al contrario, gli algoritmi che si basano sull'ordinamento o sul partizionamento in memoria presumono che tu possa rappresentare l'intero elenco in memoria. Nella forma in cui è stata posta la domanda, ciò richiederebbe O(N)
parole a 64 bit.)
@Jorn commenta che i passaggi da 1 a 3 sono una variazione del conteggio dell'ordinamento. In un certo senso ha ragione, ma le differenze sono significative:
- Un ordinamento conteggio richiede una matrice di (almeno)
Xmax - Xmin
contatori in cui Xmax
è il numero più grande nell'elenco ed Xmin
è il numero più piccolo nell'elenco. Ogni contatore deve essere in grado di rappresentare N stati; cioè assumendo una rappresentazione binaria deve avere un tipo intero (almeno) ceiling(log2(N))
bit.
- Per determinare la dimensione della matrice, un ordinamento conteggio deve eseguire un passaggio iniziale nell'elenco per determinare
Xmax
e Xmin
.
- Il requisito di spazio minimo nel caso peggiore è quindi
ceiling(log2(N)) * (Xmax - Xmin)
bit.
Al contrario, l'algoritmo presentato sopra richiede semplicemente dei N
bit nei casi peggiori e migliori.
Tuttavia, questa analisi porta all'intuizione che se l'algoritmo eseguisse un passaggio iniziale attraverso la lista cercando uno zero (e contando gli elementi della lista se necessario), darebbe una risposta più rapida senza usare alcuno spazio se trovasse lo zero. Vale sicuramente la pena farlo se c'è un'alta probabilità di trovare almeno uno zero nell'elenco. E questo passaggio extra non cambia la complessità generale.
EDIT: Ho cambiato la descrizione dell'algoritmo per utilizzare "array di booleani" poiché le persone apparentemente hanno trovato la mia descrizione originale utilizzando bit e bitmap per creare confusione.