Aquí hay una O(N)solución simple que usa O(N)espacio. Supongo que estamos restringiendo la lista de entrada a números no negativos y que queremos encontrar el primer número no negativo que no está en la lista.
- Encuentra la longitud de la lista; digamos que lo es
N.
- Asigne una matriz de
Nvalores booleanos, inicializados a todos false.
- Para cada número
Xde la lista, si Xes menor que N, establezca el X'thelemento de la matriz en true.
- Escanee la matriz comenzando desde el índice
0, buscando el primer elemento que sea false. Si encuentra el primero falseen el índice I, entonces Ies la respuesta. De lo contrario (es decir, cuando todos los elementos son true) la respuesta es N.
En la práctica, la "matriz de Nvalores booleanos" probablemente estaría codificada como un "mapa de bits" o un "conjunto de bits" representado como una matriz byteo int. Por lo general, esto utiliza menos espacio (según el lenguaje de programación) y permite que el escaneo del primero falsese realice más rápidamente.
Así es como / por qué funciona el algoritmo.
Suponga que los Nnúmeros de la lista no son distintos, o que uno o más de ellos es mayor que N. Esto significa que debe haber al menos un número en el rango 0 .. N - 1que no está en la lista. Por lo tanto, el problema de encontrar el número que falta más pequeño debe reducirse al problema de encontrar el número que falta más pequeño menor queN . Esto significa que no necesitamos realizar un seguimiento de los números que son mayores o iguales a N... porque no serán la respuesta.
La alternativa al párrafo anterior es que la lista es una permutación de los números de 0 .. N - 1. En este caso, el paso 3 establece todos los elementos de la matriz en true, y el paso 4 nos dice que el primer número "faltante" es N.
La complejidad computacional del algoritmo tiene O(N)una constante de proporcionalidad relativamente pequeña. Hace dos pasadas lineales a través de la lista, o solo una pasada si se sabe que comienza con la longitud de la lista. No es necesario representar la retención de la lista completa en la memoria, por lo que el uso de memoria asintótica del algoritmo es justo lo que se necesita para representar la matriz de valores booleanos; es decir, O(N)bits.
(Por el contrario, los algoritmos que se basan en la ordenación o la partición en memoria asumen que puede representar la lista completa en la memoria. En la forma en que se formuló la pregunta, esto requeriría O(N)palabras de 64 bits).
@Jorn comenta que los pasos 1 a 3 son una variación del ordenamiento por conteo. En cierto sentido tiene razón, pero las diferencias son significativas:
- Una ordenación de conteo requiere una matriz de (al menos)
Xmax - Xmincontadores donde Xmaxes el número más grande en la lista y Xmines el número más pequeño en la lista. Cada contador debe poder representar N estados; es decir, asumiendo una representación binaria, tiene que tener un tipo entero (al menos) ceiling(log2(N))bits.
- Para determinar el tamaño de la matriz, una clasificación de conteo debe realizar una pasada inicial a través de la lista para determinar
Xmaxy Xmin.
- Por tanto, el requisito de espacio mínimo en el peor de los casos son los
ceiling(log2(N)) * (Xmax - Xmin)bits.
Por el contrario, el algoritmo presentado anteriormente simplemente requiere Nbits en los peores y mejores casos.
Sin embargo, este análisis lleva a la intuición de que si el algoritmo hiciera una pasada inicial a través de la lista buscando un cero (y contando los elementos de la lista si fuera necesario), daría una respuesta más rápida sin ningún espacio si encontrara el cero. Definitivamente vale la pena hacer esto si hay una alta probabilidad de encontrar al menos un cero en la lista. Y este pase adicional no cambia la complejidad general.
EDITAR: He cambiado la descripción del algoritmo para usar "matriz de valores booleanos" ya que la gente aparentemente encontró confusa mi descripción original usando bits y mapas de bits.