Una pregunta de entrevista interesante que usa un colega mío:
Suponga que se le proporciona una lista muy larga y sin clasificar de enteros de 64 bits sin signo. ¿Cómo encontrarías el número entero no negativo más pequeño que no aparece en la lista?
SEGUIMIENTO: Ahora que se ha propuesto la solución obvia ordenando, ¿puede hacerlo más rápido que O (n log n)?
SEGUIMIENTO: Su algoritmo debe ejecutarse en una computadora con, digamos, 1 GB de memoria
ACLARACIÓN: La lista está en RAM, aunque puede consumir una gran cantidad de ella. Se le da el tamaño de la lista, digamos N, por adelantado.
Respuestas:
Si la estructura de datos se puede modificar en su lugar y admite el acceso aleatorio, puede hacerlo en O (N) tiempo y O (1) espacio adicional. Simplemente revise la matriz secuencialmente y para cada índice escriba el valor en el índice en el índice especificado por valor, colocando recursivamente cualquier valor en esa ubicación en su lugar y desechando valores> N. Luego, vuelva a recorrer la matriz buscando el lugar donde el valor no coincide con el índice, ese es el valor más pequeño que no está en la matriz. Esto da como resultado comparaciones de 3N como máximo y solo utiliza unos pocos valores de espacio temporal.
fuente
Aquí hay una
O(N)
solución simple que usaO(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.N
.N
valores booleanos, inicializados a todosfalse
.X
de la lista, siX
es menor queN
, establezca elX'th
elemento de la matriz entrue
.0
, buscando el primer elemento que seafalse
. Si encuentra el primerofalse
en el índiceI
, entoncesI
es la respuesta. De lo contrario (es decir, cuando todos los elementos sontrue
) la respuesta esN
.En la práctica, la "matriz de
N
valores booleanos" probablemente estaría codificada como un "mapa de bits" o un "conjunto de bits" representado como una matrizbyte
oint
. Por lo general, esto utiliza menos espacio (según el lenguaje de programación) y permite que el escaneo del primerofalse
se realice más rápidamente.Así es como / por qué funciona el algoritmo.
Suponga que los
N
números de la lista no son distintos, o que uno o más de ellos es mayor queN
. Esto significa que debe haber al menos un número en el rango0 .. N - 1
que 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 aN
... 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 entrue
, y el paso 4 nos dice que el primer número "faltante" esN
.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:
Xmax - Xmin
contadores dondeXmax
es el número más grande en la lista yXmin
es 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.Xmax
yXmin
.ceiling(log2(N)) * (Xmax - Xmin)
bits.Por el contrario, el algoritmo presentado anteriormente simplemente requiere
N
bits 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.
fuente
bool[]
un mapa de bits o mediante un mapa de bits es irrelevante para la solución general.Dado que el OP ahora ha especificado que la lista original se mantiene en RAM y que la computadora solo tiene, digamos, 1 GB de memoria, voy a arriesgarme y predecir que la respuesta es cero.
1 GB de RAM significa que la lista puede tener como máximo 134,217,728 números. Pero hay 2 64 = 18,446,744,073,709,551,616 números posibles. Entonces, la probabilidad de que cero esté en la lista es 1 en 137,438,953,472.
Por el contrario, mis probabilidades de ser alcanzado por un rayo este año son de 1 en 700.000. Y mis probabilidades de ser alcanzado por un meteorito son de aproximadamente 1 en 10 billones. Así que tengo diez veces más probabilidades de que me escriban en una revista científica debido a mi muerte prematura por un objeto celeste que la respuesta que no es cero.
fuente
Como se señaló en otras respuestas, puede hacer una clasificación y luego simplemente escanear hasta encontrar un espacio.
Puede mejorar la complejidad algorítmica a O (N) y mantener el espacio O (N) utilizando un QuickSort modificado en el que elimina las particiones que no son candidatos potenciales para contener el espacio.
Esto ahorra una gran cantidad de cálculos.
fuente
Para ilustrar una de las trampas del
O(N)
pensamiento, aquí hay unO(N)
algoritmo que usa elO(1)
espacio.fuente
Dado que los números tienen 64 bits de longitud, podemos usar el ordenamiento por radix , que es O (n). Ordénelos, luego escanéelos hasta que encuentre lo que está buscando.
si el número más pequeño es cero, avance hasta encontrar un espacio. Si el número más pequeño no es cero, la respuesta es cero.
fuente
Para un método eficiente en el espacio y todos los valores son distintos, puede hacerlo en el espacio
O( k )
y el tiempoO( k*log(N)*N )
. Es eficiente en el espacio y no hay movimiento de datos y todas las operaciones son elementales (suma y resta).U = N; L=0
k
regiones. Me gusta esto:0->(1/k)*(U-L) + L
,0->(2/k)*(U-L) + L
,0->(3/k)*(U-L) + L
...0->(U-L) + L
count{i}
) hay en cada región. (N*k
pasos)h
) que no esté llena. Eso significacount{h} < upper_limit{h}
. (k
pasos)h - count{h-1} = 1
tienes tu respuestaU = count{h}; L = count{h-1}
esto se puede mejorar usando hash (gracias a Nic por esta idea).
k
regiones. Me gusta esto:L + (i/k)->L + (i+1/k)*(U-L)
inc count{j}
utilizandoj = (number - L)/k
(if L < number < U)
h
) que no tiene k elementos en ellacount{h} = 1
h es tu respuestaU = maximum value in region h
L = minimum value in region h
Esto se ejecutará
O(log(N)*N)
.fuente
U-L < k
Simplemente los clasificaría y luego seguiría la secuencia hasta encontrar un espacio (incluido el espacio al principio entre cero y el primer número).
En términos de un algoritmo, algo como esto lo haría:
Por supuesto, si tiene mucha más memoria que el gruñido de la CPU, puede crear una máscara de bits de todos los valores posibles de 64 bits y simplemente establecer los bits para cada número de la lista. Luego busque el primer bit 0 en esa máscara de bits. Eso lo convierte en una operación O (n) en términos de tiempo pero bastante cara en términos de requisitos de memoria :-)
Dudo que puedas mejorar O (n) ya que no veo una forma de hacerlo que no implique mirar cada número al menos una vez.
El algoritmo para ese estaría en la línea de:
fuente
Ordene la lista, observe el primer y segundo elemento y comience a subir hasta que quede un hueco.
fuente
Puedes hacerlo en O (n) tiempo y O (1) espacio adicional, aunque el factor oculto es bastante grande. Esta no es una forma práctica de resolver el problema, pero podría ser interesante de todos modos.
Por cada entero de 64 bits sin signo (en orden ascendente), repita la lista hasta que encuentre el entero de destino o llegue al final de la lista. Si llega al final de la lista, el número entero de destino es el número entero más pequeño que no está en la lista. Si llega al final de los enteros de 64 bits, todos los enteros de 64 bits estarán en la lista.
Aquí está como una función de Python:
Esta función es deliberadamente ineficaz para mantenerla O (n). Tenga en cuenta especialmente que la función sigue comprobando los enteros de destino incluso después de que se ha encontrado la respuesta. Si la función regresa tan pronto como se encuentra la respuesta, la cantidad de veces que se ejecutó el ciclo externo estaría limitada por el tamaño de la respuesta, que está limitada por n. Ese cambio haría que el tiempo de ejecución fuera O (n ^ 2), aunque sería mucho más rápido.
fuente
Gracias a egon, swilden y Stephen C por mi inspiración. Primero, conocemos los límites del valor del objetivo porque no puede ser mayor que el tamaño de la lista. Además, una lista de 1 GB podría contener como máximo 134217728 (128 * 2 ^ 20) enteros de 64 bits.
Parte del
hash Propongo usar el hash para reducir drásticamente nuestro espacio de búsqueda. Primero, haz raíz cuadrada del tamaño de la lista. Para una lista de 1 GB, eso es N = 11,586. Configure una matriz de enteros de tamaño N. Repita la lista y tome la raíz cuadrada * de cada número que encuentre como su hash. En su tabla hash, incremente el contador para ese hash. A continuación, recorra su tabla hash. El primer depósito que encuentre que no sea igual a su tamaño máximo define su nuevo espacio de búsqueda.
Parte del mapa de bits
Ahora configure un mapa de bits regular igual al tamaño de su nuevo espacio de búsqueda y vuelva a recorrer la lista de fuentes, llenando el mapa de bits a medida que encuentre cada número en su espacio de búsqueda. Cuando haya terminado, el primer bit no establecido en su mapa de bits le dará su respuesta.
Esto se completará en el tiempo O (n) y el espacio O (sqrt (n)).
(* Podría usar algo como el cambio de bits para hacer esto de manera mucho más eficiente y simplemente variar el número y el tamaño de los cubos en consecuencia).
fuente
Bueno, si solo falta un número en una lista de números, la forma más fácil de encontrar el número que falta es sumar la serie y restar cada valor en la lista. El valor final es el número que falta.
fuente
fuente
Podríamos usar una tabla hash para guardar los números. Una vez que todos los números estén hechos, ejecute un contador desde 0 hasta que encontremos el más bajo. Un hash razonablemente bueno se procesará y almacenará en un tiempo constante, y se recuperará en un tiempo constante.
En el peor de los casos, si hay
n
elementos en la matriz y los hay{0, 1, ... n-1}
, en cuyo caso, la respuesta se obtendrá enn
, manteniéndolaO(n)
.fuente
Aquí está mi respuesta escrita en Java:
Idea básica: 1- Recorra la matriz desechando los números positivos, ceros y negativos duplicados mientras suma el resto, obteniendo también el número máximo positivo y conserva los números positivos únicos en un mapa.
2- Calcule la suma como max * (max + 1) / 2.
3- Encuentra la diferencia entre las sumas calculadas en los pasos 1 y 2
4- Repita el bucle desde 1 hasta el mínimo de [diferencia de sumas, máximo] y devuelva el primer número que no está en el mapa poblado en el paso 1.
fuente
Como señaló con inteligencia Stephen C, la respuesta debe ser un número menor que la longitud de la matriz. Entonces encontraría la respuesta mediante una búsqueda binaria. Esto optimiza el peor de los casos (por lo que el entrevistador no puede atraparlo en un escenario patológico de 'qué pasaría si'). En una entrevista, señale que está haciendo esto para optimizar en el peor de los casos.
La forma de utilizar la búsqueda binaria es restar el número que está buscando de cada elemento de la matriz y verificar los resultados negativos.
fuente
Me gusta la aplicación de "adivinar cero". Si los números fueran aleatorios, cero es muy probable. Si el "examinador" estableció una lista no aleatoria, agregue una y adivine nuevamente:
El peor de los casos es n * N con n = N, pero en la práctica es muy probable que n sea un número pequeño (por ejemplo, 1)
fuente
No estoy seguro de haber recibido la pregunta. Pero si para la lista 1, 2, 3, 5, 6 y el número que falta es 4, entonces el número que falta se puede encontrar en O (n) por: (n + 2) (n + 1) / 2- (n + 1) n / 2
EDITAR: lo siento, supongo que estaba pensando demasiado rápido anoche. De todos modos, la segunda parte debería ser reemplazada por sum (lista), que es donde viene O (n). La fórmula revela la idea detrás de ella: para n enteros secuenciales, la suma debe ser (n + 1) * n / 2. Si falta un número, la suma sería igual a la suma de (n + 1) números enteros secuenciales menos el número faltante.
Gracias por señalar el hecho de que estaba poniendo algunas piezas intermedias en mi mente.
fuente
¡Bien hecho Ants Aasma! Pensé en la respuesta durante unos 15 minutos y de forma independiente se me ocurrió una respuesta similar a la tuya:
m representa "la salida máxima posible actual dado lo que sé sobre las primeras i entradas y suponiendo nada más sobre los valores hasta la entrada en m-1".
Este valor de m se devolverá solo si (a [i], ..., a [m-1]) es una permutación de los valores (i, ..., m-1). Así, si a [i]> = mo si a [i] <i o si a [i] == a [a [i]] sabemos que m es la salida incorrecta y debe ser al menos un elemento menor. Entonces, decrementando my intercambiando a [i] con a [m] podemos recurrir.
Si esto no es cierto, pero a [i]> i, entonces sabiendo que a [i]! = A [a [i]] sabemos que intercambiar a [i] con a [a [i]] aumentará el número de elementos en su propio lugar.
De lo contrario, a [i] debe ser igual a i, en cuyo caso podemos incrementar i sabiendo que todos los valores de hasta e incluido este índice son iguales a su índice.
La prueba de que esto no puede entrar en un bucle infinito se deja como ejercicio al lector. :)
fuente
El fragmento de Dafny de la respuesta de Ants muestra por qué el algoritmo in situ puede fallar. La
requires
condición previa describe que los valores de cada elemento no deben ir más allá de los límites de la matriz.Pegue el código en el validador con y sin la
forall ...
cláusula para ver el error de verificación. El segundo error es el resultado de que el verificador no puede establecer una condición de terminación para el bucle de Paso 1. Demostrar esto queda en manos de alguien que entienda mejor la herramienta.fuente
Aquí hay una respuesta en Java que no modifica la entrada y usa tiempo O (N) y N bits más una pequeña sobrecarga constante de memoria (donde N es el tamaño de la lista):
fuente
Obtuve el 100% para la solución anterior.
fuente
1) Filtrar negativo y cero
2) Clasificar / diferenciar
3) Visita matriz
Complejidad : O (N) u O (N * log (N))
usando Java8
fuente
Se puede usar un unordered_set para almacenar todos los números positivos, y luego podemos iterar desde 1 hasta la longitud de unordered_set y ver el primer número que no ocurre.
fuente
Solución mediante javascript básico
var a = [1, 3, 6, 4, 1, 2]; function findSmallest(a) { var m = 0; for(i=1;i<=a.length;i++) { j=0;m=1; while(j < a.length) { if(i === a[j]) { m++; } j++; } if(m === 1) { return i; } } } console.log(findSmallest(a))
Espero que esto ayude a alguien.
fuente
Con python no es el más eficiente, pero correcto
fuente
fuente
esto puede ayudar:
fuente