Encuentra el número entero más pequeño que no está en una lista

87

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.

PeterAllenWebb
fuente
6
Creo que puedes omitir la parte no negativa, viendo cómo estás hablando de un entero sin signo.
KevenDenen
4
La pregunta es bastante básica, a menos que esté muy fuera de la base, en mi opinión, pero, como otros han mencionado, hay preguntas que hacer o suposiciones que deben establecerse.
James Black
8
@paxdiablo: Este es un caso en el que decir O (n) no significa mucho. Incluso si almacena su matriz de 2 ^ 64 bits en tabletas de arcilla en la Isla de Pascua y accede a ella mediante una paloma mensajera, el algoritmo sigue siendo O (n).
IJ Kennedy
6
Cambiar los requisitos de memoria a la mitad hace que esta sea una gran pregunta de entrevista ;-)
Chris Ballance
1
Creo que es divertido que todas las respuestas hagan la misma solución general (ordenar la matriz y encontrar el primer valor que rompa la secuencia), pero todas usan un tipo diferente. (Modificado clasificación rápida, Radix sort, ...) La respuesta aceptada es equivalente a un recuento tipo que elementos de los descartes que superen N.
Joren

Respuestas:

121

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.

# Pass 1, move every value to the position of its value
for cursor in range(N):
    target = array[cursor]
    while target < N and target != array[target]:
        new_target = array[target]
        array[target] = target
        target = new_target

# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
    if array[cursor] != cursor:
        return cursor
return N
Hormigas Aasma
fuente
9
Pequeño quisquilloso. Se ha perdido un caso trivial: cuando la lista es {0, ..., N-1}. En ese caso, el paso 1 no hace nada y en el paso 2 matriz [cursor] == cursor para todas las entradas en la lista, por lo que el algoritmo no regresa. Por lo que necesita una declaración 'return N' al final.
Alex
12
Su solución combina el dominio y el rango (el objetivo es tanto un valor como un índice). El rango está limitado por el almacenamiento disponible a elementos de 128M, sin embargo, el dominio tiene un tamaño de 2G. Fallará con una sola entrada con un valor mayor que el número de entradas que se pueden asignar a la matriz. Si la pregunta no especificó 'muy larga', la respuesta es elegante, incluso si destruye la entrada. La compensación tiempo-espacio es muy evidente en este problema, y ​​una solución O (N) puede no ser posible bajo las restricciones proporcionadas.
Pekka
2
El segundo paso podría usar búsqueda binaria en lugar de búsqueda lineal.
user448810
4
Esta solución funciona solo si el rango de valores y el índice son comparables.
Dubby
7
Funcionará bien con valores más grandes. Los valores más grandes se pueden ignorar porque no pueden tener nada que ver con el valor más pequeño que no está en la matriz. Para su ejemplo, la primera pasada recorrerá la matriz ignorando todos los valores debido al objetivo <N y luego devolverá 0 en la primera iteración de la segunda pasada.
Hormigas Aasma
89

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.

  1. Encuentra la longitud de la lista; digamos que lo es N.
  2. Asigne una matriz de Nvalores booleanos, inicializados a todos false.
  3. Para cada número Xde la lista, si Xes menor que N, establezca el X'thelemento de la matriz en true.
  4. 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.

Esteban C
fuente
3
@ adi92 Si el paso 3 le da un mapa de bits con todos los bits establecidos en 1, entonces la lista contiene todos los valores de 0 a N-1. Eso significa que el número entero no negativo más pequeño de la lista es N. Si hay algún valor entre 0 y N-1 que NO esté en la lista, el bit correspondiente no se establecerá. Por tanto, el valor más pequeño es la respuesta.
divegeek
4
@ adi92 En su ejemplo, la lista contendría 300 elementos. Eso significa que si hay algún valor "faltante", debe ser menor que 300. Al ejecutar el algoritmo, crearíamos un campo de bits con 300 ranuras, luego estableceríamos repetidamente los bits en las ranuras 1, 2 y 3, dejando todos los las otras ranuras, 0 y 4 a 299, están libres. Al escanear el campo de bits, encontraríamos la bandera en la ranura 0 limpia, por lo que sabríamos que 0 es la respuesta.
divegeek
4
Tenga en cuenta que este algoritmo podría entenderse de manera más simple sin el giro de bits: "Cree una matriz booleana de tamaño N", etc. Una vez que lo entienda de esa manera, pasar a una versión bit a bit es conceptualmente fácil.
Jon Skeet
2
Al dar una solución abstracta, utilice la forma conceptualmente más simple que funcione y no se especialice demasiado. Su solución pide a gritos el uso de una matriz booleana (abstracta), así que llámelo así. El hecho de que pueda implementar esta matriz mediante bool[]un mapa de bits o mediante un mapa de bits es irrelevante para la solución general.
Joren
2
Creo que esta solución podría describirse mejor con "Use una ordenación de conteo que ignore los elementos por encima de N, luego encuentre el primer elemento que falta haciendo una búsqueda lineal desde el principio".
Joren
13

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.

Barry Brown
fuente
11
Su cálculo solo es válido si los valores se distribuyen uniformemente y se seleccionan al azar. También podrían haberse generado de forma secuencial.
divegeek
1
Tienes razón, por supuesto. Pero me refiero a optimizar para el caso común. :)
Barry Brown
10
Entonces, ¿cuáles son las probabilidades de que el entrevistado sea seleccionado con esta respuesta?
Amarghosh
6
La pregunta no dice que los números se seleccionen uniformemente al azar. Son seleccionados por la persona que establece esta pregunta. Dado esto, la probabilidad de que 0 esté en la lista es mucho mayor que 1 en 137,438,953,472, probablemente incluso mayor que 1 en 2. :-)
ShreevatsaR
8
@Amarghosh La respuesta a esa pregunta también es cero.
PeterAllenWebb
10

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.

  • En la primera fase de partición, elimine los duplicados.
  • Una vez que se completa la partición, mire la cantidad de elementos en la partición inferior
  • ¿Es este valor igual al valor utilizado para crear la partición?
    • Si es así, implica que la brecha está en la partición superior.
      • Continúe con la clasificación rápida, ignorando la partición inferior
    • De lo contrario, el espacio está en la partición inferior.
      • Continúe con la clasificación rápida, ignorando la partición superior

Esto ahorra una gran cantidad de cálculos.

cdiggins
fuente
Eso es bastante ingenioso. Supondría que puede calcular la longitud de la partición en menos de un tiempo lineal, lo que se puede hacer si se almacena junto con la matriz de partición. También asume que la lista original se mantiene en RAM.
Barry Brown
2
Si conoce la longitud de la lista, también puede eliminar cualquier valor mayor que len (lista). Según el principio de casillero, cualquier "agujero" debe ser menor que len (lista).
divegeek
1
No creo que sea O (n) ... Por un lado, no estoy seguro de que pueda eliminar los duplicados hasta que la lista esté completamente ordenada. En segundo lugar, si bien puede garantizar el desecho de la mitad del espacio de búsqueda en cada iteración (porque se ha dividido en por debajo y por encima del punto medio), todavía tiene múltiples pasadas (que dependen de n) sobre datos que dependen de n.
paxdiablo
1
paxdiablo: puede crear una nueva lista con valores únicos utilizando un método de mapa de bits como el que propuso Stephen C. Esto ocurre en O (n) tiempo y espacio. No estoy seguro de si se puede hacer mejor que eso.
Nic
9

Para ilustrar una de las trampas del O(N)pensamiento, aquí hay un O(N)algoritmo que usa el O(1)espacio.

for i in [0..2^64):
  if i not in list: return i

print "no 64-bit integers are missing"
IJ Kennedy
fuente
1
Will tiene razón. Esto no es O (n) porque en realidad tiene dos bucles aquí, pero uno está implícito. Determinar si un valor está en una lista es una operación O (n), y lo está haciendo n veces en su ciclo for. Eso lo convierte en O (n ^ 2).
Nic
6
Nic, Will, es O (n * N) donde n es el tamaño de la lista y N es el tamaño del dominio (enteros de 64 bits). Si bien N es un número enorme, sigue siendo una constante, por lo que formalmente la complejidad del problema es O (n).
Ants Aasma
1
Hormigas, estoy de acuerdo en que es O (n N), pero N no es constante. Debido a que el algoritmo finaliza cuando encuentra la respuesta, el número de iteraciones completas a través del ciclo externo es igual a la respuesta, que a su vez está limitada por el tamaño de la lista. Entonces, O (N n) es O (n ^ 2) en este caso.
Will Harris
12
Buscar un número en una lista de N elementos es claramente O (N). Hacemos esto 2 ^ 64 veces. Aunque es grande, 2 ^ 64 es CONSTANTE. Por lo tanto, el algoritmo es C * O (N), que sigue siendo O (N).
IJ Kennedy
3
Debo retractarme de mi declaración anterior; según la definición más estricta, esta operación es de hecho O (n).
Nic
8

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.

Barry Brown
fuente
Es cierto, pero los requisitos de memoria pueden volverse bastante intensos para el ordenamiento por radix.
PeterAllenWebb
1
La ordenación por radix no funcionará para conjuntos de datos muy grandes. Pero el ordenamiento por partición y base podría funcionar.
DarthVader
5

Para un método eficiente en el espacio y todos los valores son distintos, puede hacerlo en el espacio O( k )y el tiempo O( k*log(N)*N ). Es eficiente en el espacio y no hay movimiento de datos y todas las operaciones son elementales (suma y resta).

  1. conjunto U = N; L=0
  2. Primero particione el espacio numérico en kregiones. 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
  3. Encuentra cuántos números ( count{i}) hay en cada región. ( N*kpasos)
  4. Busque la primera región ( h) que no esté llena. Eso significa count{h} < upper_limit{h}. ( kpasos)
  5. si h - count{h-1} = 1tienes tu respuesta
  6. conjunto U = count{h}; L = count{h-1}
  7. ir a 2

esto se puede mejorar usando hash (gracias a Nic por esta idea).

  1. mismo
  2. Primero particione el espacio numérico en kregiones. Me gusta esto:
    • L + (i/k)->L + (i+1/k)*(U-L)
  3. inc count{j} utilizando j = (number - L)/k (if L < number < U)
  4. encuentra la primera región ( h) que no tiene k elementos en ella
  5. si count{h} = 1h es tu respuesta
  6. conjunto U = maximum value in region h L = minimum value in region h

Esto se ejecutará O(log(N)*N).

Egon
fuente
Realmente me gusta esta respuesta. Fue un poco difícil de leer, pero es muy similar a lo que tenía en mi cabeza cuando leí la pregunta.
Nic
También en algún momento sería inteligente cambiar a esa solución de mapa de bits de Stephen C. probablemente cuandoU-L < k
Egon
Esto no se ejecuta en O (log (N) * N) sino en O (N). Su respuesta es una generalización de la respuesta de @cdiggins y se ejecuta en O (N) porque sum (1 / k ** i for i in range (ceil (log_k (n)))) <= 2.
Lapinot
En cada iteración pasa por O (N) números, toma O (log_k (N)) iteraciones totales. Por tanto, O (log_k (N) * N) == O (log (N) * N). Los números originales no están ordenados / agrupados y debe revisarlos todos.
Egon
Pero si dividió la lista original en k regiones (de tamaño n / k), entonces selecciona la primera región que no está llena. Por lo tanto, en la siguiente iteración, solo necesita considerar la región seleccionada y dividirla en k regiones nuevas (de tamaño n / k ** 2), etc. ?).
Lapinot
3

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:

def smallest_not_in_list(list):
    sort(list)
    if list[0] != 0:
        return 0
    for i = 1 to list.last:
        if list[i] != list[i-1] + 1:
            return list[i-1] + 1
    if list[list.last] == 2^64 - 1:
        assert ("No gaps")
    return list[list.last] + 1

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:

def smallest_not_in_list(list):
    bitmask = mask_make(2^64) // might take a while :-)
    mask_clear_all (bitmask)
    for i = 1 to list.last:
        mask_set (bitmask, list[i])
    for i = 0 to 2^64 - 1:
        if mask_is_clear (bitmask, i):
            return i
    assert ("No gaps")
paxdiablo
fuente
De la descripción parece excluir 0 al primer elemento, ya que es el más pequeño que no está en la lista. Pero, esa es una suposición que hice, podría estar equivocado.
James Black
Mi pensamiento era que si la secuencia ordenada fuera 4, 5, 6, entonces 0 sería el más pequeño que no estuviera en la lista.
paxdiablo
Espero que 2, 3, 5, la respuesta sea 4, pero podría estar equivocado.
James Black
Una pregunta que debería ser respondida por el OP. ¿El espacio de búsqueda es "todos los enteros sin signo de 64 bits" o "todos los números entre el más bajo y el más alto de la lista"?
paxdiablo
Estoy de acuerdo en que, en el peor de los casos, debes mirar al menos una vez, a menos que ya esté ordenado en un árbol binario.
James Black
2

Ordene la lista, observe el primer y segundo elemento y comience a subir hasta que quede un hueco.

James Black
fuente
Depende de cómo se defina, no en la lista.
James Black
@PeterAllenWebb - Los habrá, pero ¿están los números en orden aleatorio o ordenados?
James Black
1

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:

def smallest_missing_uint64(source_list):
    the_answer = None

    target = 0L
    while target < 2L**64:

        target_found = False
        for item in source_list:
            if item == target:
                target_found = True

        if not target_found and the_answer is None:
            the_answer = target

        target += 1L

    return the_answer

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.

Will Harris
fuente
Cierto. Es divertido ver cuán horriblemente algunos de los algoritmos que son O (1) espacio y O (n) tiempo fallan en la práctica con esta pregunta.
PeterAllenWebb
1

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

Nic
fuente
1
Me gusta la idea de dividir el espacio de búsqueda en cubos Root-N para reducir la huella de memoria, pero los duplicados en la lista romperían este método. Me pregunto si se puede arreglar.
PeterAllenWebb
Tienes razón, me olvidé de considerar las entradas duplicadas. No estoy seguro de que se pueda solucionar.
Nic
1

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.

Jeff Lundstrom
fuente
Si. Esa es otra pregunta clásica de la entrevista.
PeterAllenWebb
1
Incluso más fácil que eso es XOR los números en la lista juntos, XOR los números en el rango juntos, y XOR los resultados juntos.
John Kurlak
1
 int i = 0;
            while ( i < Array.Length)
            {

                if (Array[i] == i + 1)
                {
                    i++;
                }

                if (i < Array.Length)
                {
                    if (Array[i] <= Array.Length)
                    {//SWap

                        int temp = Array[i];
                        int AnoTemp = Array[temp - 1];
                        Array[temp - 1] = temp;
                        Array[i] = AnoTemp;

                    }
                    else
                       i++;



                }
            }

            for (int j = 0; j < Array.Length; j++)
            {
                if (Array[j] > Array.Length)
                {
                    Console.WriteLine(j + 1);
                    j = Array.Length;
                }
                else
                    if (j == Array.Length - 1)
                        Console.WriteLine("Not Found !!");

            }
        }
ranjeet
fuente
1

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.

for every i in X         // One scan Θ(1)
   hashtable.put(i, i);  // O(1)

low = 0;

while (hashtable.get(i) <> null)   // at most n+1 times
   low++;

print low;

En el peor de los casos, si hay nelementos en la matriz y los hay {0, 1, ... n-1}, en cuyo caso, la respuesta se obtendrá en n, manteniéndola O(n).

Milind C
fuente
1

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.

public static int solution(int[] A) {
    if (A == null || A.length == 0) {
        throw new IllegalArgumentException();
    }

    int sum = 0;
    Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
    int max = A[0];
    for (int i = 0; i < A.length; i++) {
        if(A[i] < 0) {
            continue;
        }
        if(uniqueNumbers.get(A[i]) != null) {
            continue;
        }
        if (A[i] > max) {
            max = A[i];
        }
        uniqueNumbers.put(A[i], true);
        sum += A[i];
    }
    int completeSum = (max * (max + 1)) /  2;
    for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
        if(uniqueNumbers.get(j) == null) { //O(1)
            return j;
        }
    }
    //All negative case
    if(uniqueNumbers.isEmpty()) {
        return 1;
    }
    return 0;
}
Rami
fuente
0

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.

Emilio M Bumachar
fuente
0

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:

LowNum=0
i=0
do forever {
  if i == N then leave /* Processed entire array */
  if array[i] == LowNum {
     LowNum++
     i=0
     }
   else {
     i++
   }
}
display LowNum

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)

NealB
fuente
0

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.

Codismo
fuente
1
No veo, a primera vista, cómo funcionaría esto. En su caso, n = 5 y la formulera será fija, sin importar qué número falte.
sisve
Simon: ¿Podrías ahora eliminar el voto negativo de acuerdo con mi edición?
Codismo
0

¡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:

#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
    int m = n;
    for (int i = 0; i < m;) {
        if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
            m--;
            SWAP (a[i], a[m]);
            continue;
        }
        if (a[i] > i) {
            SWAP (a[i], a[a[i]]);
            continue;
        }
        i++;
    }
    return m;
}

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. :)

Paul Hsieh
fuente
0

El fragmento de Dafny de la respuesta de Ants muestra por qué el algoritmo in situ puede fallar. La requirescondición previa describe que los valores de cada elemento no deben ir más allá de los límites de la matriz.

method AntsAasma(A: array<int>) returns (M: int)
  requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
  modifies A; 
{
  // Pass 1, move every value to the position of its value
  var N := A.Length;
  var cursor := 0;
  while (cursor < N)
  {
    var target := A[cursor];
    while (0 <= target < N && target != A[target])
    {
        var new_target := A[target];
        A[target] := target;
        target := new_target;
    }
    cursor := cursor + 1;
  }

  // Pass 2, find first location where the index doesn't match the value
  cursor := 0;
  while (cursor < N)
  {
    if (A[cursor] != cursor)
    {
      return cursor;
    }
    cursor := cursor + 1;
  }
  return N;
}

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.

Pekka
fuente
0

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

int smallestMissingValue(List<Integer> values) {
    BitSet bitset = new BitSet(values.size() + 1);
    for (int i : values) {
        if (i >= 0 && i <= values.size()) {
            bitset.set(i);
        }
    }
    return bitset.nextClearBit(0);
}
Dave L.
fuente
0
def solution(A):

index = 0
target = []
A = [x for x in A if x >=0]

if len(A) ==0:
    return 1

maxi = max(A)
if maxi <= len(A):
    maxi = len(A)

target = ['X' for x in range(maxi+1)]
for number in A:
    target[number]= number

count = 1
while count < maxi+1:
    if target[count] == 'X':
        return count
    count +=1
return target[count-1] + 1

Obtuve el 100% para la solución anterior.

Angelo
fuente
0

1) Filtrar negativo y cero

2) Clasificar / diferenciar

3) Visita matriz

Complejidad : O (N) u O (N * log (N))

usando Java8

public int solution(int[] A) {
            int result = 1;
    boolean found = false;
    A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
    //System.out.println(Arrays.toString(A));
    for (int i = 0; i < A.length; i++) {
        result = i + 1;
        if (result != A[i]) {
            found = true;
            break;
        }
    }
    if (!found && result == A.length) {
        //result is larger than max element in array
        result++;
    }
    return result;
}
Abdullah Lubbadeh
fuente
0

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.

int firstMissingPositive(vector<int>& nums) {

    unordered_set<int> fre;
    // storing each positive number in a hash.
    for(int i = 0; i < nums.size(); i +=1)
    {
        if(nums[i] > 0)
            fre.insert(nums[i]);
     }

    int i = 1;
    // Iterating from 1 to size of the set and checking 
    // for the occurrence of 'i'

    for(auto it = fre.begin(); it != fre.end(); ++it)
    {
        if(fre.find(i) == fre.end())
            return i;
        i +=1;
    }

    return i;
}
Mohit Anand
fuente
0

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.

Mano
fuente
0

Con python no es el más eficiente, pero correcto

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime

# write your code in Python 3.6

def solution(A):
    MIN = 0
    MAX = 1000000
    possible_results = range(MIN, MAX)

    for i in possible_results:
        next_value = (i + 1)
        if next_value not in A:
            return next_value
    return 1

test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))
smentek
fuente
0
def solution(A):
    A.sort()
    j = 1
    for i, elem in enumerate(A):
        if j < elem:
            break
        elif j == elem:
            j += 1
            continue
        else:
            continue
    return j
orfeu
fuente
0

esto puede ayudar:

0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length;                            (O(1))
2- initialize B Cells With 1;                                  (O(n))
3- For Each Item In A:
        if (B.Length <= item) then B[Item] = -1                (O(n))
4- The answer is smallest index in B such that B[index] != -1  (O(n))
Hamed
fuente
¿Esto difiere de la respuesta de Stephen C ? ¿Cómo?
greybeard