Voici une O(N)
solution simple qui utilise l' O(N)
espace. Je suppose que nous limitons la liste d'entrée aux nombres non négatifs et que nous voulons trouver le premier nombre non négatif qui n'est pas dans la liste.
- Trouvez la longueur de la liste; disons que c'est
N
.
- Alloue un tableau de
N
booléens, initialisé à tous false
.
- Pour chaque nombre
X
de la liste, si X
est inférieur à N
, définissez l' X'th
élément du tableau sur true
.
- Parcourez le tableau à partir de l'index
0
, à la recherche du premier élément false
. Si vous trouvez le premier false
à l'index I
, alors I
est la réponse. Sinon (c'est-à-dire lorsque tous les éléments le sont true
), la réponse est N
.
En pratique, le "tableau de N
booléens" serait probablement codé comme un "bitmap" ou un "jeu de bits" représenté par un tableau byte
ou int
. Cela utilise généralement moins d'espace (selon le langage de programmation) et permet false
d'effectuer plus rapidement l'analyse du premier .
C'est comment / pourquoi l'algorithme fonctionne.
Supposons que les N
nombres de la liste ne soient pas distincts ou qu'un ou plusieurs d'entre eux soient supérieurs à N
. Cela signifie qu'il doit y avoir au moins un nombre dans la plage 0 .. N - 1
qui n'est pas dans la liste. Le problème de trouver le plus petit nombre manquant doit donc se réduire au problème de trouver le plus petit nombre manquant inférieur àN
. Cela signifie que nous n'avons pas besoin de garder une trace des nombres supérieurs ou égaux à N
... car ils ne seront pas la réponse.
L'alternative au paragraphe précédent est que la liste est une permutation des nombres de 0 .. N - 1
. Dans ce cas, l'étape 3 définit tous les éléments du tableau sur true
, et l'étape 4 nous indique que le premier nombre «manquant» est N
.
La complexité de calcul de l'algorithme est O(N)
avec une constante de proportionnalité relativement petite. Il effectue deux passages linéaires dans la liste, ou un seul passage si la longueur de la liste est connue pour commencer. Il n'est pas nécessaire de représenter l'ensemble de la liste en mémoire, de sorte que l'utilisation asymptotique de la mémoire de l'algorithme est exactement ce qui est nécessaire pour représenter le tableau de booléens; c'est-à-dire des O(N)
bits.
(En revanche, les algorithmes qui reposent sur le tri ou le partitionnement en mémoire supposent que vous pouvez représenter la liste entière en mémoire. Dans la forme où la question a été posée, cela nécessiterait O(N)
des mots de 64 bits.)
@Jorn commente que les étapes 1 à 3 sont une variante du tri par comptage. Dans un sens, il a raison, mais les différences sont importantes:
- Un tri par comptage nécessite un tableau de (au moins)
Xmax - Xmin
compteurs où Xmax
est le plus grand nombre de la liste et Xmin
le plus petit nombre de la liste. Chaque compteur doit pouvoir représenter N états; c'est-à-dire en supposant une représentation binaire, il doit avoir un type entier (au moins) ceiling(log2(N))
bits.
- Pour déterminer la taille du tableau, un tri par comptage doit effectuer un premier passage dans la liste pour déterminer
Xmax
et Xmin
.
- L'espace minimal requis dans le pire des cas est donc de
ceiling(log2(N)) * (Xmax - Xmin)
bits.
En revanche, l'algorithme présenté ci-dessus nécessite simplement des N
bits dans le pire et le meilleur des cas.
Cependant, cette analyse conduit à l'intuition que si l'algorithme effectuait un premier passage dans la liste à la recherche d'un zéro (et en comptant les éléments de la liste si nécessaire), il donnerait une réponse plus rapide en utilisant aucun espace du tout s'il trouvait le zéro. Cela vaut vraiment la peine de le faire s'il y a une forte probabilité de trouver au moins un zéro dans la liste. Et cette passe supplémentaire ne change pas la complexité globale.
EDIT: J'ai changé la description de l'algorithme pour utiliser "tableau de booléens" car les gens ont apparemment trouvé ma description originale utilisant des bits et des bitmaps pour être déroutante.