Genere un número entero que no se encuentre entre los cuatro mil millones dados

691

Me han dado esta pregunta de entrevista:

Dado un archivo de entrada con cuatro mil millones de enteros, proporcione un algoritmo para generar un entero que no esté contenido en el archivo. Suponga que tiene 1 GB de memoria. Haga un seguimiento de lo que haría si tuviera solo 10 MB de memoria.

Mi analisis:

El tamaño del archivo es 4 × 10 9 × 4 bytes = 16 GB.

Podemos hacer una ordenación externa, lo que nos permite conocer el rango de los enteros.

Mi pregunta es ¿cuál es la mejor manera de detectar el entero que falta en los conjuntos de enteros grandes ordenados?

Mi entendimiento (después de leer todas las respuestas):

Suponiendo que estamos hablando de enteros de 32 bits, hay 2 32 = 4 * 10 9 enteros distintos.

Caso 1: tenemos 1 GB = 1 * 10 9 * 8 bits = memoria de 8 mil millones de bits.

Solución:

Si usamos un bit que representa un número entero distinto, es suficiente. No necesitamos ordenar.

Implementación:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

Caso 2: 10 MB de memoria = 10 * 10 6 * 8 bits = 80 millones de bits

Solución:

Para todos los posibles prefijos de 16 bits, hay 2 16 número de enteros = 65536, necesitamos 2 16 * 4 * 8 = 2 millones de bits. Necesitamos construir 65536 cubos. Para cada segmento, necesitamos 4 bytes que contengan todas las posibilidades porque el peor de los casos es que todos los 4 mil millones de enteros pertenecen al mismo segmento.

  1. Construya el contador de cada cubo a través de la primera pasada a través del archivo.
  2. Escanee los cubos, encuentre el primero que tenga menos de 65536 golpes.
  3. Cree nuevos cubos cuyos altos prefijos de 16 bits se encuentran en el paso 2 al segundo paso del archivo
  4. Escanee los cubos construidos en el paso 3, encuentre el primer cubo que no tenga éxito.

El código es muy similar al anterior.

Conclusión: disminuimos la memoria al aumentar el paso del archivo.


Una aclaración para quienes llegan tarde: la pregunta, como se hizo, no dice que haya exactamente un número entero que no esté contenido en el archivo, al menos esa no es la forma en que la mayoría de las personas lo interpreta. Sin embargo, muchos comentarios en el hilo de comentarios son sobre esa variación de la tarea. Desafortunadamente, el comentario que lo introdujo en el hilo de comentarios fue eliminado más tarde por su autor, por lo que ahora parece que las respuestas huérfanas simplemente lo malinterpretaron todo. Es muy confuso, lo siento.

SecureFish
fuente
32
@trashgod, mal. Para 4294967295 enteros únicos, tendrá 1 entero restante. Para encontrarlo, debe sumar todos los enteros y restarlo de la suma precalculada de todos los enteros posibles.
Nakilon
58
Esta es la segunda "perla" de "Perlas de programación", y le sugiero que lea toda la discusión en el libro. Ver books.google.com/…
Alok Singhal
8
@ Richard un 64 bit int sería más que suficientemente grande.
cftarnas
79
int getMissingNumber(File inputFile) { return 4; }( referencia )
johnny
14
No importa que no pueda almacenar la suma de todos los enteros de 1 a 2 ^ 32 porque el tipo entero en lenguajes como C / C ++ SIEMPRE conserva propiedades como la asociatividad y la comunicatividad. Lo que esto significa es que, aunque la suma no será la respuesta correcta, si calcula el esperado con desbordamiento, la suma real con desbordamiento y luego resta, el resultado seguirá siendo correcto (siempre que no se desborde).
thedayturns

Respuestas:

530

Suponiendo que "entero" significa 32 bits : 10 MB de espacio es más que suficiente para contar cuántos números hay en el archivo de entrada con cualquier prefijo de 16 bits dado, para todos los posibles prefijos de 16 bits en una pasada a través del fichero de entrada. Al menos uno de los cubos habrá sido golpeado menos de 2 16 veces. Haga un segundo pase para encontrar cuál de los números posibles en ese cubo ya se usa.

Si significa más de 32 bits, pero aún de tamaño acotado : haga lo anterior, ignorando todos los números de entrada que se encuentren fuera del rango de 32 bits (con o sin signo; su elección).

Si "entero" significa entero matemático : lea la entrada una vez y realice un seguimiento de la longitud del número más grande del número más largo que haya visto. Cuando haya terminado, envíe el máximo más uno un número aleatorio que tenga un dígito más. (Uno de los números en el archivo puede ser un bignum que requiere más de 10 MB para representarse exactamente, pero si la entrada es un archivo, entonces al menos puede representar la longitud de cualquier cosa que quepa en él).

hmakholm dejó a Monica
fuente
24
Perfecto. ¡Su primera respuesta requiere solo 2 pases a través del archivo!
corsiKa
47
¿Un bignum de 10 MB? Eso es bastante extremo.
Mark Ransom
12
@Legate, solo omita los números grandes y no haga nada al respecto. Dado que de todos modos no va a generar un número demasiado grande, no es necesario realizar un seguimiento de cuáles de ellos ha visto.
hmakholm dejó a Mónica el
12
Lo bueno de la Solución 1 es que puede disminuir la memoria aumentando los pases.
Yousf
11
@Barry: la pregunta anterior no indica que falte exactamente un número. Tampoco dice que los números en el archivo no se repiten. (Seguir la pregunta realmente formulada es probablemente una buena idea en una entrevista, ¿verdad? ;-))
Christopher Creutzig
197

Los algoritmos informados estadísticamente resuelven este problema usando menos pases que los enfoques deterministas.

Si se permiten enteros muy grandes, se puede generar un número que probablemente sea único en el tiempo O (1). Un entero pseudoaleatorio de 128 bits como un GUID solo colisionará con uno de los cuatro billones de enteros existentes en el conjunto en menos de uno de cada 64 billones de billones de casos.

Si los enteros están limitados a 32 bits , se puede generar un número que probablemente sea único en una sola pasada utilizando mucho menos de 10 MB. Las probabilidades de que un entero de 32 bits pseudoaleatorio choque con uno de los 4 mil millones de enteros existentes es de aproximadamente el 93% (4e9 / 2 ^ 32). La probabilidad de que 1000 enteros pseudoaleatorios colisionen es menor a uno en 12,000 billones de billones (probabilidad de colisión ^ 1000). Entonces, si un programa mantiene una estructura de datos que contiene 1000 candidatos pseudoaleatorios e itera a través de los enteros conocidos, eliminando coincidencias de los candidatos, es casi seguro encontrar al menos un entero que no esté en el archivo.

Ben Haley
fuente
32
Estoy bastante seguro de que los enteros están delimitados. Si no lo fueran, incluso un programador principiante pensaría en el algoritmo "tome una pasada a través de los datos para encontrar el número máximo y agregue 1"
Adrian Petrescu
12
Adivinar literalmente una salida aleatoria probablemente no le dará muchos puntos en una entrevista
Brian Gordon
66
@Adrian, tu solución parece obvia (y fue para mí, la usé en mi propia respuesta) pero no es obvia para todos. Es una buena prueba para ver si puede detectar soluciones obvias o si va a complicar demasiado todo lo que toca.
Mark Ransom el
19
@Brian: Creo que esta solución es imaginativa y práctica. Por mi parte, daría muchas felicitaciones por esta respuesta.
Richard H
66
Ah, aquí se encuentra la línea entre ingenieros y científicos. Gran respuesta Ben!
TrojanName
142

Una discusión detallada sobre este problema se ha discutido en Jon Bentley "Columna 1. Cracking the Oyster" Perlas de programación Addison-Wesley pp.3-10

Bentley analiza varios enfoques, incluida la ordenación externa, la ordenación por fusión utilizando varios archivos externos, etc., pero el mejor método que sugiere Bentley es un algoritmo de un solo paso que utilice campos de bits , que llama con humor "Wonder Sort" :) Llegando al problema, 4 mil millones los números se pueden representar en:

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

El código para implementar el conjunto de bits es simple: (tomado de la página de soluciones )

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

El algoritmo de Bentley realiza una sola pasada sobre el archivo, setmarca el bit apropiado en la matriz y luego examina esta matriz usando la testmacro de arriba para encontrar el número que falta.

Si la memoria disponible es inferior a 0,466 GB, Bentley sugiere un algoritmo de k-pass, que divide la entrada en rangos dependiendo de la memoria disponible. Para tomar un ejemplo muy simple, si solo estuviera disponible 1 byte (es decir, memoria para manejar 8 números) y el rango fuera de 0 a 31, dividimos esto en rangos de 0 a 7, 8-15, 16-22 y así sucesivamente y maneje este rango en cada uno de los 32/8 = 4pases.

HTH

viña
fuente
12
No conozco el libro, pero no hay razón para llamarlo "Wonder Sort", ya que es solo un bucketort, con un contador de 1 bit.
flolo
3
Aunque es más portátil, este código será aniquilado por un código escrito para utilizar instrucciones de vectores compatibles con hardware . Sin embargo, creo que gcc puede convertir automáticamente el código para usar operaciones vectoriales en algunos casos.
Brian Gordon el
3
@brian No creo que Jon Bentley permitiera tales cosas en su libro sobre algoritmos.
David Heffernan el
8
@BrianGordon, el tiempo dedicado a ram será insignificante en comparación con el tiempo dedicado a leer el archivo. Olvídate de optimizarlo.
Ian
1
@BrianGordon: ¿O estabas hablando del bucle al final para encontrar el primer bit sin configurar? Sí, los vectores acelerarán eso, pero recorriendo el campo de bits con enteros de 64 bits, buscando uno que != -1aún saturará el ancho de banda de la memoria que se ejecuta en un solo núcleo (esto es SIMD dentro de un registro, SWAR, con bits como elementos). (Para diseños recientes de Intel / AMD). Solo tiene que averiguar qué bit no está establecido después de encontrar la ubicación de 64 bits que lo contiene. (Y para eso not / lzcntpuede hacerlo). Es cierto que el bucle en una prueba de un solo bit puede no optimizarse bien.
Peter Cordes
120

Dado que el problema no especifica que tenemos que encontrar el número más pequeño posible que no esté en el archivo, podríamos generar un número que sea más largo que el archivo de entrada en sí. :)

Andris
fuente
66
A menos que el número más grande en el archivo sea max int, simplemente se desbordará
KBusc
¿Cuál sería el tamaño de ese archivo en un programa del mundo real que podría necesitar generar un nuevo entero y agregarlo al archivo de "enteros usados" 100 veces?
Michael
2
Estaba pensando esto. Suponiendo que intes 32bits, solo salida 2^64-1. Hecho.
imallett
1
Si es una int por línea tr -d '\n' < nums.txt > new_num.txt:: D
Shon
56

Para la variante de 1 GB de RAM, puede usar un vector de bits. Debe asignar 4 mil millones de bits == 500 MB de matriz de bytes. Para cada número que lea de la entrada, establezca el bit correspondiente a '1'. Una vez que hayas terminado, itera sobre los bits, encuentra el primero que sigue siendo '0'. Su índice es la respuesta.

Itay Maman
fuente
44
No se especifica el rango de números en la entrada. ¿Cómo funciona este algoritmo si la entrada consta de todos los números pares entre 8 mil millones y 16 mil millones?
Mark Ransom
27
@ Mark, simplemente ignore las entradas que están fuera del rango 0..2 ^ 32. De todos modos, no va a generar ninguno de ellos, por lo que no es necesario recordar cuál de ellos evitar.
hmakholm dejó a Mónica el
@ Marque cualquier algoritmo que use para determinar cómo una cadena de 32 bits se asigna a un número real depende de usted. El proceso sigue siendo el mismo. La única diferencia es cómo lo imprime como un número real en la pantalla.
corsiKa
44
En lugar de iterar usted mismo, puede usar bitSet.nextClearBit(0): download.oracle.com/javase/6/docs/api/java/util/…
starblue
3
Sería útil mencionar que, independientemente del rango de los enteros, se garantiza que al menos un bit sea 0 al final del pase. Esto se debe al principio del casillero.
Rafał Dowgird
46

Si son enteros de 32 bits (probablemente de la elección de ~ 4 mil millones de números cercanos a 2 32 ), su lista de 4 mil millones de números ocupará como máximo el 93% de los posibles enteros (4 * 10 9 / (2 32 ) ) Entonces, si crea una matriz de bits de 2 32 bits bits con cada bit inicializado a cero (que ocupará 2 29 bytes ~ 500 MB de RAM; recuerde un byte = 2 3 bits = 8 bits), lea su lista entera y para cada int establece el elemento de matriz de bits correspondiente de 0 a 1; y luego lea su matriz de bits y devuelva el primer bit que todavía es 0.

En el caso de que tenga menos RAM (~ 10 MB), esta solución debe modificarse ligeramente. 10 MB ~ 83886080 bits todavía son suficientes para hacer una matriz de bits para todos los números entre 0 y 83886079. Para que pueda leer su lista de entradas; y solo registre # que estén entre 0 y 83886079 en su matriz de bits. Si los números se distribuyen al azar; con una probabilidad abrumadora (difiere en un 100% en aproximadamente 10 -2592069 ) se encuentra un int que falta). De hecho, si solo elige los números del 1 al 2048 (con solo 256 bytes de RAM), todavía encontrará un número perdido en un porcentaje abrumador (99.99999999999999999999999999999999999999999999999999999999999995%) del tiempo.

Pero digamos en lugar de tener unos 4 mil millones de números; tenías algo así como 2 32 - 1 números y menos de 10 MB de RAM; entonces cualquier rango pequeño de entradas solo tiene una pequeña posibilidad de no contener el número.

Si se le garantizó que cada int en la lista era único, podría sumar los números y restar la suma con un # faltante a la suma total (½) (2 32 ) (2 32 - 1) = 9223372034707292160 para encontrar el int faltante . Sin embargo, si se produjo un int dos veces, este método fallará.

Sin embargo, siempre puedes dividir y conquistar. Un método ingenuo sería leer la matriz y contar el número de números que están en la primera mitad (0 a 2 31 -1) y la segunda mitad (2 31 , 2 32 ). Luego elija el rango con menos números y repita dividiendo ese rango a la mitad. (Diga si hubiera dos números menos en (2 31 , 2 32 ), entonces su próxima búsqueda contaría los números en el rango (2 31 , 3 * 2 30 -1), (3 * 2 30 , 2 32 ). repita hasta que encuentre un rango con números cero y tenga su respuesta. Debe tomar O (lg N) ~ 32 lecturas a través de la matriz.

Ese método fue ineficiente. Solo estamos usando dos enteros en cada paso (o aproximadamente 8 bytes de RAM con un entero de 4 bytes (32 bits)). Un mejor método sería dividir en sqrt (2 32 ) = 2 16 = 65536 contenedores, cada uno con 65536 números en un contenedor. Cada bin requiere 4 bytes para almacenar su recuento, por lo que necesita 2 18 bytes = 256 kB. Entonces bin 0 es (0 a 65535 = 2 16 -1), bin 1 es (2 16 = 65536 a 2 * 2 16 -1 = 131071), bin 2 es (2 * 2 16 = 131072 a 3 * 2 16 - 1 = 196607). En python tendrías algo como:

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

Lea la lista de ~ 4 mil millones de enteros; y cuente cuántas entradas caen en cada uno de los 2 16 contenedores y encuentre un incomplete_bin que no tenga todos los 65536 números. Luego lees la lista entera de 4 mil millones nuevamente; pero esta vez solo nota cuando los enteros están en ese rango; volteando un poco cuando los encuentras.

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break
dr jimbob
fuente
3
Una respuesta tan asombrosa. Esto realmente funcionaría; y tiene resultados garantizados.
Jonathan Dickinson el
@dr jimbob, ¿qué pasa si solo hay un número en un contenedor y ese único número tiene 65535 duplicados? Si es así, el contenedor seguirá contando 65536, pero todos los números 65536 son el mismo.
Alcott
@Alcott: supuse que tenía 2 ^ 32-1 (o menos) números, por lo que, según el principio del casillero, tiene la garantía de tener un contenedor con menos de 65536 recuentos para verificar con más detalle. Estamos tratando de encontrar solo un número entero perdido, no todos. Si tenía 2 ^ 32 o más números, no puede garantizar que falte un número entero y no podría utilizar este método (o tener una garantía desde el principio de que falta un número entero). Su mejor apuesta sería la fuerza bruta (p. Ej., Lea la matriz 32 veces; verifique los primeros 65536 # la primera vez y pare una vez que se encuentre una respuesta).
dr jimbob
El ingenioso método upper-16 / lower-16 fue publicado anteriormente por Henning: stackoverflow.com/a/7153822/224132 . Sin embargo, me encantó la idea de agregar un conjunto único de enteros a los que les falta exactamente un miembro.
Peter Cordes
3
@PeterCordes: Sí, la solución de Henning es anterior a la mía, pero creo que mi respuesta sigue siendo útil (trabajando en varias cosas con más detalle). Dicho esto, Jon Bentley en su libro Programming Pearls sugirió una opción de pasadas múltiples para este problema (ver la respuesta de Vinethth) mucho antes de que existiera stackoverflow (no es que esté afirmando que ninguno de nosotros conscientemente robó allí o que Bentley fue el primero en analizar este problema: es una solución bastante natural para desarrollar). Dos pasos parecen más naturales cuando la limitación es que ya no tiene suficiente memoria para una solución de 1 paso con una matriz de bits gigante.
dr jimbob
37

¿Por qué hacerlo tan complicado? ¿Pides un número entero no presente en el archivo?

De acuerdo con las reglas especificadas, lo único que necesita almacenar es el número entero más grande que encontró hasta ahora en el archivo. Una vez que se haya leído todo el archivo, devuelva un número 1 mayor que ese.

No hay riesgo de golpear maxint ni nada, porque de acuerdo con las reglas, no hay restricción en el tamaño del número entero o el número devuelto por el algoritmo.

Pete
fuente
44
Esto funciona a menos que el int max fue en el archivo, lo que es muy posible ...
Pearsonartphoto
13
Las reglas no especifican que es de 32 bits o 64 bits ni nada, por lo que de acuerdo con las reglas especificadas, no hay un máximo de int. Entero no es un término informático, es un término matemático que identifica números enteros positivos o negativos.
Pete el
Es cierto, pero no se puede suponer que es un número de 64 bits, o que alguien no se escabullirá en el número máximo int solo para confundir tales algoritmos.
PearsonArtPhoto
24
La noción completa de "max int" no es válida en el contexto si no se ha especificado un lenguaje de programación. Por ejemplo, mire la definición de Python de un entero largo. Es ilimitado No hay techo. Siempre puedes agregar uno. Asume que se está implementando en un idioma que tiene un valor máximo permitido para un entero.
Pete el
32

Esto se puede resolver en muy poco espacio utilizando una variante de búsqueda binaria.

  1. Comience con el rango permitido de números, 0a 4294967295.

  2. Calcule el punto medio.

  3. Recorra el archivo, contando cuántos números fueron iguales, menores o mayores que el valor del punto medio.

  4. Si no hay números iguales, ya está. El número del punto medio es la respuesta.

  5. De lo contrario, elija el rango que tenga la menor cantidad de números y repita desde el paso 2 con este nuevo rango.

Esto requerirá hasta 32 escaneos lineales a través del archivo, pero solo usará unos pocos bytes de memoria para almacenar el rango y los recuentos.

Esto es esencialmente lo mismo que la solución de Henning , excepto que usa dos contenedores en lugar de 16k.

hammar
fuente
2
Es con lo que comencé, antes de comenzar a optimizar los parámetros dados.
hmakholm dejó a Mónica el
@ Henning: Genial. Es un buen ejemplo de un algoritmo en el que es fácil modificar la compensación del espacio-tiempo.
hammar
@hammar, pero ¿qué pasa si hay esos números que aparecen más de una vez?
Alcott
@Alcott: entonces el algoritmo elegirá el contenedor más denso en lugar del contenedor más disperso, pero según el principio del casillero, nunca podrá elegir un contenedor completamente lleno. (El menor de los dos recuentos siempre será menor que el rango del contenedor).
Peter Cordes
27

EDITAR Ok, esto no se pensó bien, ya que supone que los enteros en el archivo siguen alguna distribución estática. Aparentemente no necesitan hacerlo, pero incluso entonces uno debería intentar esto:


Hay ≈4.3 mil millones de enteros de 32 bits. No sabemos cómo se distribuyen en el archivo, pero el peor de los casos es el que tiene la mayor entropía de Shannon: una distribución igual. En este caso, la probabilidad de que un entero no ocurra en el archivo es

((2³²-1) / 2³²) ⁴ ⁰⁰⁰ ⁰⁰⁰ ⁰⁰⁰ ≈ .4

Cuanto más baja es la entropía de Shannon, mayor es la probabilidad promedio, pero incluso en el peor de los casos, tenemos una probabilidad del 90% de encontrar un número no recurrente después de 5 suposiciones con enteros aleatorios. Simplemente cree dichos números con un generador pseudoaleatorio, guárdelos en una lista. Luego lea int tras int y compárelo con todas sus conjeturas. Cuando haya una coincidencia, elimine esta entrada de la lista. Después de haber revisado todo el archivo, es probable que le quede más de una suposición. Usa cualquiera de ellos. En el caso raro (10% incluso en el peor de los casos) de que no quede ninguna conjetura, obtenga un nuevo conjunto de enteros aleatorios, tal vez más esta vez (10-> 99%).

Consumo de memoria: unas pocas docenas de bytes, complejidad: O (n), sobrecarga: desglosable, ya que la mayor parte del tiempo se dedicará a los inevitables accesos al disco duro en lugar de comparar ints de todos modos.


El peor de los casos reales, cuando no asumimos una distribución estática, es que cada número entero ocurre max. una vez, porque entonces solo 1 - 4000000000 / 2³² ≈ 6% de todos los enteros no aparecen en el archivo. Por lo tanto, necesitará algunas conjeturas más, pero eso aún no costará cantidades dañinas de memoria.

a la izquierda
fuente
55
Me alegra ver que alguien más pensó en esto, pero ¿por qué está aquí abajo en la parte inferior? Esto es algo de 1 pasada ... 10 MB es suficiente para conjeturas de 2.5 M, y 93% ^ 2.5M ≈ 10 ^ -79000 es realmente una posibilidad insignificante de necesitar un segundo escaneo. Debido a la sobrecarga de la búsqueda binaria, ¡va más rápido si usa menos conjeturas! Esto es óptimo tanto en tiempo como en espacio.
Potatoswatter
1
@Potatoswatter: bueno, mencionaste la búsqueda binaria. Probablemente no valga la pena la sobrecarga cuando se usan solo 5 conjeturas, pero ciertamente es de 10 o más. Incluso podría hacer las conjeturas de 2 M, pero luego debería almacenarlas en un conjunto de hash para obtener O (1) para la búsqueda.
Leftaroundabout
1
La respuesta equivalente de @Potatoswatter Ben Haley está cerca de la cima
Brian Gordon
1
Me gusta este enfoque, pero sugeriría una mejora de ahorro de memoria: si uno tiene N bits de almacenamiento indexado disponible, más algo de almacenamiento constante, defina una función de codificación reversible configurable de 32 bits (permutación), elija una permutación arbitraria y borre todo bits indexados Luego lea cada número del archivo, codifíquelo y, si el resultado es menor que N, establezca el bit correspondiente. Si no se establece ningún bit al final del archivo, invierta la función de codificación en su índice. Con 64 KB de memoria, uno podría probar efectivamente la disponibilidad de más de 512,000 números en una sola pasada.
supercat
2
Por supuesto, con este algoritmo, el peor de los casos es aquel en el que los números fueron creados por el mismo generador de números aleatorios que está utilizando. Suponiendo que puede garantizar que ese no es el caso, su mejor táctica es usar un generador de números aleatorios congruenciales lineales para generar su lista, de modo que recorra el espacio numérico de forma pseudoaleatoria. Eso significa que si de alguna manera fallas, puedes continuar generando números hasta que hayas cubierto todo el rango de entradas (de haber encontrado una brecha), sin duplicar tu esfuerzo.
Dewi Morgan
25

Si le falta un número entero del rango [0, 2 ^ x - 1], entonces simplemente póngalos a la vez. Por ejemplo:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(Sé que esto no responde la pregunta exactamente , pero es una buena respuesta a una pregunta muy similar).

rfrankel
fuente
1
Sí, es fácil probar [ ] que funciona cuando falta un entero, pero con frecuencia falla si falta más de uno. Por ejemplo, 0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7es 0. [ Escribiendo 2 x para 2 a la potencia x, y a ^ b para a xor b, el xor de todos k <2 x es cero - k ^ ~ k = (2 ^ x) - 1 para k <2 ^ (x-1) y k ^ ~ k ^ j ^ ~ j = 0 cuando j = k + 2 ** (x-2), por lo que el valor xor de todos menos uno es el valor del desaparecido]
James Waldby - jwpat7
2
Como mencioné en un comentario sobre la respuesta de ircmaxell: El problema no dice "falta un número", dice encontrar un número no incluido en los 4 mil millones de números en el archivo. Si asumimos números enteros de 32 bits, es posible que falten alrededor de 300 millones de números en el archivo. La probabilidad de que el xor de los números presentes coincida con un número faltante es solo del 7%.
James Waldby - jwpat7
Esta es la respuesta en la que estaba pensando cuando leí inicialmente la pregunta, pero en una inspección más cercana creo que la pregunta es más ambigua que esta. Para su información, esta es la pregunta en la que estaba pensando: stackoverflow.com/questions/35185/…
Lee Netherton
18

Es posible que estén buscando ver si ha oído hablar de un filtro Bloom probabilístico que puede determinar de manera muy eficiente absolutamente si un valor no es parte de un conjunto grande (pero solo puede determinar con alta probabilidad si es miembro del conjunto).

Pablo
fuente
44
Con probablemente más del 90% de los valores posibles establecidos, su filtro Bloom probablemente necesitaría degenerar en el campo de bits que muchas respuestas ya usan. De lo contrario, terminarás con una cadena de bits inútil completamente llena.
Christopher Creutzig
@Christopher Mi comprensión de los filtros Bloom es que no obtienes un bitarray lleno hasta que alcanzas el 100%
Paul
... de lo contrario obtendrías falsos negativos.
Paul
@Paul una matriz de bits llena le da falsos positivos, que están permitidos. En este caso, el filtro de floración probablemente degeneraría en el caso en que la solución, que sería negativa, arroje un falso positivo.
ataylor
1
@Paul: puede obtener un bitarray lleno tan pronto como el número de funciones hash multiplicado por el número de entradas sea tan grande como la longitud de su campo. Por supuesto, ese sería un caso excepcional, pero la probabilidad aumentará bastante rápido.
Christopher Creutzig
17

Según la redacción actual de la pregunta original, la solución más simple es:

Encuentre el valor máximo en el archivo, luego agregue 1.

oosterwal
fuente
55
¿Qué pasa si el MAXINT está incluido en el archivo?
Petr Peller
@Petr Peller: una biblioteca BIGINT esencialmente eliminaría las limitaciones en el tamaño entero.
oosterwal
2
@oosterwal, si esta respuesta fue permitida, entonces ni siquiera necesita leer el archivo, simplemente imprima el mayor número posible.
Nakilon
1
@oosterwal, si su gran número aleatorio era el más grande que podía imprimir, y estaba en el archivo, entonces esta tarea no podría resolverse.
Nakilon
3
@Nakilon: +1 Tu punto está tomado. Es aproximadamente equivalente a calcular la cantidad total de dígitos en el archivo e imprimir un número con esa cantidad de dígitos.
oosterwal
14

Utilizar un BitSet . 4 billones de enteros (suponiendo hasta 2 ^ 32 enteros) empaquetados en un BitSet a 8 por byte son 2 ^ 32/2 ^ 3 = 2 ^ 29 = aproximadamente 0.5 Gb.

Para agregar un poco más de detalle: cada vez que lea un número, configure el bit correspondiente en el BitSet. Luego, pase sobre BitSet para encontrar el primer número que no está presente. De hecho, podría hacer esto con la misma eficacia seleccionando repetidamente un número aleatorio y probando si está presente.

En realidad, BitSet.nextClearBit (0) le dirá el primer bit no establecido.

Al observar la API de BitSet, parece que solo admite 0..MAX_INT, por lo que puede necesitar 2 BitSets, uno para números + y cinco números for-have, pero los requisitos de memoria no cambian.

dty
fuente
1
O si no quieres usar un BitSet... prueba una matriz de bits. Hace lo mismo;)
jcolebrand
12

Si no hay límite de tamaño, la forma más rápida es tomar la longitud del archivo y generar la longitud del archivo + 1 número de dígitos aleatorios (o simplemente "11111 ..." s). Ventaja: ni siquiera necesita leer el archivo, y puede minimizar el uso de memoria casi a cero. Desventaja: imprimirá miles de millones de dígitos.

Sin embargo, si el único factor fuera minimizar el uso de memoria, y nada más es importante, esta sería la solución óptima. Incluso podría obtener el premio "peor abuso de las reglas".

vsz
fuente
11

Si suponemos que el rango de números siempre será 2 ^ n (una potencia par de 2), entonces exclusivo o funcionará (como se muestra en otro póster). En cuanto a por qué, demostrémoslo:

La teoría

Dado cualquier rango de enteros basado en 0 que tenga 2^nelementos con un elemento faltante, puede encontrar ese elemento faltante simplemente haciendo un xoring de los valores conocidos para obtener el número faltante.

La prueba

Veamos n = 2. Para n = 2, podemos representar 4 enteros únicos: 0, 1, 2, 3. Tienen un patrón de bits de:

  • 0 - 00
  • 1 - 01
  • 2 - 10
  • 3 - 11

Ahora, si miramos, cada bit se establece exactamente dos veces. Por lo tanto, dado que se establece un número par de veces, y exclusivo-o de los números producirá 0. Si falta un solo número, el exclusivo-o producirá un número que cuando se ofrece exclusivo con el número faltante dará como resultado 0. Por lo tanto, el número faltante y el número exclusivo resultante son exactamente iguales. Si eliminamos 2, el xor resultante será10 (o 2).

Ahora, veamos n + 1. Llamemos al número de veces que se establece cada bit n, xy al número de veces que se establece cada bit n+1 y. El valor de yserá igual a y = x * 2porque hay xelementos con el n+1bit establecido en 0 y xelementos con el n+1bit establecido en 1. Y dado 2xque siempre será par, n+1siempre tendrá cada bit establecido un número par de veces.

Por lo tanto, dado que n=2funciona y n+1funciona, el método xor funcionará para todos los valores de n>=2.

El algoritmo para rangos basados ​​en 0

Esto es bastante simple Utiliza 2 * n bits de memoria, por lo que para cualquier rango <= 32, funcionarán 2 enteros de 32 bits (ignorando cualquier memoria consumida por el descriptor de archivo). Y hace una sola pasada del archivo.

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

El algoritmo para rangos arbitrarios

Este algoritmo funcionará para rangos de cualquier número inicial a cualquier número final, siempre que el rango total sea igual a 2 ^ n ... Esto básicamente re-basa el rango para tener el mínimo en 0. Pero requiere 2 pases a través del archivo (el primero para obtener el mínimo, el segundo para calcular el int faltante).

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

Rangos arbitrarios

Podemos aplicar este método modificado a un conjunto de rangos arbitrarios, ya que todos los rangos cruzarán una potencia de 2 ^ n al menos una vez. Esto funciona solo si falta un bit. Se necesitan 2 pases de un archivo sin clasificar, pero encontrará el único número que falta cada vez:

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

Básicamente, re-basa el rango alrededor de 0. Luego, cuenta el número de valores sin clasificar para agregar mientras calcula el exclusivo-o. Luego, agrega 1 al recuento de valores no ordenados para cuidar el valor faltante (cuente el valor faltante). Luego, siga xorreando el valor de n, incrementado en 1 cada vez hasta que n sea una potencia de 2. El resultado se vuelve a basar en la base original. Hecho.

Aquí está el algoritmo que probé en PHP (usando una matriz en lugar de un archivo, pero el mismo concepto):

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

Alimentado en una matriz con cualquier rango de valores (probé incluyendo negativos) con uno dentro de ese rango que falta, encontró el valor correcto cada vez.

Otro enfoque

Dado que podemos usar la ordenación externa, ¿por qué no simplemente buscar una brecha? Si suponemos que el archivo está ordenado antes de ejecutar este algoritmo:

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;
ircmaxell
fuente
3
El problema no dice "falta un número", dice encontrar un número no incluido en los 4 mil millones de números en el archivo. Si asumimos números enteros de 32 bits, es posible que falten alrededor de 300 millones de números en el archivo. La probabilidad de que el xor de los números presentes coincida con un número faltante es solo del 7%.
James Waldby - jwpat7
Si tiene un rango contiguo pero perdido uno que no está basado en cero, agregue en lugar de xor. sum(0..n) = n*(n+1)/2. Por lo tanto missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[]). (Suma idea de la respuesta de @ hammar.)
Peter Cordes
9

Pregunta capciosa, a menos que se haya citado incorrectamente. Simplemente lea el archivo una vez para obtener el número entero máximo ny regrese n+1.

Por supuesto, necesitaría un plan de respaldo en caso de que se n+1produzca un desbordamiento de enteros.

Mark Ransom
fuente
3
Aquí hay una solución que funciona ... excepto cuando no lo hace. ¡Útil! :-)
dty
A menos que se haya citado incorrectamente, la pregunta no puso un límite en el tipo de entero, ni siquiera en el lenguaje utilizado. Muchos idiomas modernos tienen enteros limitados únicamente por la memoria disponible. Si el número entero más grande en el archivo es> 10 MB, mala suerte, tarea imposible para el segundo caso. Mi solución favorita
Jürgen Strobel
9

Verifique el tamaño del archivo de entrada, luego envíe cualquier número que sea demasiado grande para ser representado por un archivo de ese tamaño. Esto puede parecer un truco barato, pero es una solución creativa para un problema de entrevista, evita el problema de memoria y técnicamente es O (n).

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

Debe imprimir 10 bitcount - 1 , que siempre será mayor que 2 bitcount . Técnicamente, el número que tiene que vencer es 2 bitcount - (4 * 10 9 - 1) , ya que sabe que hay (4 mil millones - 1) otros enteros en el archivo, e incluso con una compresión perfecta ocuparán al menos un bit cada uno

Justin Morgan
fuente
¿Por qué no solo en Console.Write( 1 << bitcount )lugar del bucle? Si hay n bits en el archivo, entonces cualquier número (_n_ + 1) -bit con un 1 inicial está absolutamente garantizado que será más grande.
Emmet
@Emmet: eso solo causaría un desbordamiento de enteros, a menos que el archivo fuera más pequeño que el tamaño de un int (4 bytes en C #). C ++ puede permitirle usar algo más grande, pero C # no parece permitir nada más que entradas de 32 bits con el <<operador. De cualquier manera, a menos que enrolle su propio tipo entero gigantesco, será un tamaño de archivo muy pequeño. Demostración: rextester.com/BLETJ59067
Justin Morgan
8
  • El enfoque más simple es encontrar el número mínimo en el archivo y devolver 1 menos que eso. Utiliza el almacenamiento O (1) y el tiempo O (n) para un archivo de n números. Sin embargo, fallará si el rango de números es limitado, lo que podría hacer que min-1 no sea un número.

  • Ya se ha mencionado el método simple y directo de usar un mapa de bits. Ese método utiliza O (n) tiempo y almacenamiento.

  • También se ha mencionado un método de 2 pasadas con 2 ^ 16 cubos de conteo. Lee 2 * n enteros, por lo que utiliza el tiempo O (n) y el almacenamiento O (1), pero no puede manejar conjuntos de datos con más de 2 ^ 16 números. Sin embargo, se extiende fácilmente a (p. Ej.) 2 ^ 60 enteros de 64 bits ejecutando 4 pases en lugar de 2, y se adapta fácilmente para usar una memoria pequeña usando solo tantos contenedores como caben en la memoria y aumentando el número de pases correspondientemente, en en cuyo caso, el tiempo de ejecución ya no es O (n) sino que es O (n * log n).

  • El método de XOR 'unir todos los números juntos, mencionado hasta ahora por rfrankel y por último por ircmaxell responde a la pregunta formulada en stackoverflow # 35185 , como señaló ltn100. Utiliza el almacenamiento O (1) y el tiempo de ejecución O (n). Si por el momento asumimos enteros de 32 bits, XOR tiene una probabilidad del 7% de producir un número distinto. Justificación: dado ~ 4G números distintos XOR'd juntos, y ca. 300M no en archivo, el número de bits establecidos en cada posición de bit tiene la misma posibilidad de ser par o impar. Por lo tanto, 2 ^ 32 números tienen la misma probabilidad de surgir que el resultado XOR, de los cuales el 93% ya están en el archivo. Tenga en cuenta que si los números en el archivo no son todos distintos, aumenta la probabilidad de éxito del método XOR.

James Waldby - jwpat7
fuente
7

Por alguna razón, tan pronto como leí este problema, pensé en la diagonalización. Estoy asumiendo enteros arbitrariamente grandes.

Lee el primer número. Izquierda con cero bits hasta que tenga 4 mil millones de bits. Si el primer bit (de orden superior) es 0, salida 1; de lo contrario, salida 0. (Realmente no tiene que dejar el pad izquierdo: solo genera un 1 si no hay suficientes bits en el número). Haga lo mismo con el segundo número, excepto que use su segundo bit. Continúe a través del archivo de esta manera. Producirá un número de 4 mil millones de bits un bit a la vez, y ese número no será el mismo que ninguno en el archivo. Prueba: era lo mismo que el enésimo número, luego estarían de acuerdo con el enésimo bit, pero no lo hacen por construcción.

Jonathan Amsterdam
fuente
+1 para creatividad (y la salida más pequeña en el peor de los casos para una solución de un solo paso)
hmakholm dejó a Mónica el
Pero no hay 4 mil millones de bits para diagonalizar, solo hay 32. Simplemente terminarás con un número de 32 bits que es diferente de los primeros 32 números de la lista.
Brian Gordon el
@Henning No es un solo pase; todavía tienes que convertir de unario a binario. Editar: Bueno, supongo que es una pasada sobre el archivo. No importa.
Brian Gordon el
@Brian, ¿dónde hay algo "unario" aquí? La respuesta es construir una respuesta binaria un bit cada vez, y solo lee el archivo de entrada una vez, haciéndolo pasar una sola vez. (Si se requiere una salida decimal , las cosas se vuelven problemáticas, entonces probablemente sea mejor construir un dígito decimal por cada tres números de entrada y aceptar un aumento del 10% en el registro del número de salida).
hmakholm dejó a Mónica el
2
@Henning El problema no tiene sentido para los enteros arbitrariamente grandes porque, como han señalado muchas personas, es trivial encontrar el número más grande y agregar uno, o construir un número muy largo del archivo mismo. Esta solución de diagonalización es particularmente inapropiada porque, en lugar de ramificarse en el ibit th, podría generar 1 bit 4 mil millones de veces y arrojar un 1 adicional al final. Estoy de acuerdo con tener enteros arbitrariamente grandes en el algoritmo, pero creo que el problema es generar un entero de 32 bits que falta. Simplemente no tiene sentido de otra manera.
Brian Gordon
6

Puede usar banderas de bits para marcar si un entero está presente o no.

Después de recorrer todo el archivo, escanee cada bit para determinar si el número existe o no.

Suponiendo que cada número entero sea de 32 bits, encajarán convenientemente en 1 GB de RAM si se realiza la marcación de bits.

Shamim Hafiz
fuente
0,5 Gb, a menos que haya redefinido bytes a ser 4 bits ;-)
dty
2
@dty Creo que quiere decir "cómodamente", ya que habrá mucho espacio en 1Gb.
corsiKa
6

Elimine el espacio en blanco y los caracteres no numéricos del archivo y agregue 1. Su archivo ahora contiene un solo número que no figura en el archivo original.

De Reddit por Carbonetc.

Ashley
fuente
¡Quiéralo! Aunque no es exactamente la respuesta que estaba buscando ...: D
Johann du Toit
6

Solo por completar, aquí hay otra solución muy simple, que probablemente tomará mucho tiempo en ejecutarse, pero usa muy poca memoria.

Deje que todos los enteros posibles sean el rango de int_mina int_max, y bool isNotInFile(integer)una función que devuelve verdadero si el archivo no contiene un cierto entero y falso más (comparando ese entero con cada entero en el archivo)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}
deg
fuente
La pregunta era exactamente sobre el algoritmo para la isNotInFilefunción. Asegúrese de comprender la pregunta antes de responder.
Aleks G
2
no, la pregunta era "qué número entero no está en el archivo", no "es número entero x en el archivo". una función para determinar la respuesta a la última pregunta podría, por ejemplo, comparar cada número entero en el archivo con el número entero en cuestión y devolver verdadero en una coincidencia.
deg
Creo que esta es una respuesta legítima. Excepto para E / S, solo necesita un número entero y una bandera de bool.
Brian Gordon
@Aleks G - No veo por qué esto está marcado como incorrecto. Todos estamos de acuerdo en que es el algoritmo más lento de todos :-), pero funciona y solo necesita 4 bytes para leer el archivo. La pregunta original no estipula que el archivo solo se puede leer una vez, por ejemplo.
Simon Mourier
1
@Aleks G - Correcto. Nunca dije que dijiste eso tampoco. Simplemente decimos que IsNotInFile se puede implementar de manera trivial usando un bucle en el archivo: Abrir; Mientras que no es de {Leer entero; Devolver falso si Integer = i; Else Continue;}. Solo necesita 4 bytes de memoria.
Simon Mourier
5

Para la restricción de memoria de 10 MB:

  1. Convierta el número a su representación binaria.
  2. Cree un árbol binario donde left = 0 y right = 1.
  3. Inserte cada número en el árbol usando su representación binaria.
  4. Si ya se ha insertado un número, las hojas ya se habrán creado.

Cuando termine, simplemente tome una ruta que no se haya creado antes para crear el número solicitado.

4.000 millones = 2 ^ 32, lo que significa que 10 MB podrían no ser suficientes.

EDITAR

Es posible una optimización, si se han creado dos hojas finales y tienen un elemento primario común, entonces se pueden eliminar y marcar el elemento primario como no una solución. Esto corta ramas y reduce la necesidad de memoria.

EDITAR II

No hay necesidad de construir el árbol completamente también. Solo necesita construir ramas profundas si los números son similares. Si también cortamos ramas, entonces esta solución podría funcionar de hecho.

Jérôme Verstrynge
fuente
66
... y cómo encajará en 10 MB?
hmakholm dejó a Mónica el
Qué tal: limite la profundidad de BTree a algo que cabe en 10MB; esto significaría que tendría resultados en el conjunto {falso positivo | positivo} y podría iterar a través de eso y usar otras técnicas para encontrar valores.
Jonathan Dickinson el
5

Contestaré la versión de 1 GB:

No hay suficiente información en la pregunta, por lo que expondré algunas suposiciones primero:

El entero es de 32 bits con un rango de -2,147,483,648 a 2,147,483,647.

Pseudocódigo:

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}
BobTurbo
fuente
4

Mientras hagamos respuestas creativas, aquí hay otra.

Use el programa de ordenamiento externo para ordenar el archivo de entrada numéricamente. Esto funcionará para cualquier cantidad de memoria que pueda tener (usará el almacenamiento de archivos si es necesario). Lea el archivo ordenado y muestre el primer número que falta.

Rhialto apoya a Monica
fuente
3

Eliminación de bits

Una forma es eliminar bits, sin embargo, esto podría no producir un resultado (lo más probable es que no lo haga). Psuedocódigo:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

Bit Counts

Lleve un registro de los recuentos de bits; y use los bits con la menor cantidad para generar un valor. Nuevamente, esto no tiene garantía de generar un valor correcto.

Lógica de rango

Mantenga un registro de una lista de rangos ordenados (ordenados por inicio). Un rango está definido por la estructura:

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

Revise cada valor en el archivo e intente eliminarlo del rango actual. Este método no tiene garantías de memoria, pero debería funcionar bastante bien.

Jonathan Dickinson
fuente
3

2 128 * 10 18 + 1 (que es (2 8 ) 16 * 10 18 + 1) - ¿no puede ser una respuesta universal para hoy? Esto representa un número que no se puede guardar en un archivo 16 EB, que es el tamaño máximo de archivo en cualquier sistema de archivos actual.

Michael Sagalovich
fuente
¿Y cómo imprimirías el resultado? No puede ponerlo en un archivo, y la impresión en la pantalla llevaría unos pocos miles de millones de años. No es probable que se logre un tiempo de actividad con las computadoras actuales.
vsz
nunca se dice que necesitamos imprimir el resultado en ningún lado, solo 'generarlo'. entonces depende de lo que quieres decir con generar. de todos modos, mi respuesta es solo un truco para evitar desarrollar un algoritmo real :)
Michael Sagalovich
3

Creo que este es un problema resuelto (ver arriba), pero hay un caso secundario interesante a tener en cuenta porque podría preguntarse:

Si hay exactamente 4,294,967,295 (2 ^ 32 - 1) enteros de 32 bits sin repeticiones y, por lo tanto, solo falta uno, hay una solución simple.

Comience un total acumulado en cero, y para cada número entero en el archivo, agregue ese número entero con un desbordamiento de 32 bits (efectivamente, runningTotal = (runningTotal + nextInteger)% 4294967296). Una vez completado, agregue 4294967296/2 al total acumulado, nuevamente con un desbordamiento de 32 bits. Resta esto de 4294967296, y el resultado es el entero que falta.

El problema de "solo un entero faltante" se puede resolver con una sola ejecución y solo 64 bits de RAM dedicados a los datos (32 para el total acumulado, 32 para leer en el siguiente entero).

Corolario: la especificación más general es extremadamente simple de igualar si no nos preocupa cuántos bits debe tener el resultado entero. Simplemente generamos un número entero lo suficientemente grande como para que no pueda estar contenido en el archivo que se nos proporciona. De nuevo, esto ocupa una RAM absolutamente mínima. Ver el pseudocódigo.

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}
Syntaera
fuente
@Nakilon y TheDayTurns lo han señalado en los comentarios a la pregunta original
Brian Gordon, el
3

Como Ryan lo dijo básicamente, ordena el archivo y luego revisa los enteros y cuando se omite un valor allí, lo tienes :)

EDITAR en downvoters: el OP mencionó que el archivo podría clasificarse, por lo que este es un método válido.

monstruo de trinquete
fuente
Una parte crucial es que debes hacerlo a medida que avanzas, de esa manera solo tienes que leer una vez. Acceder a la memoria física es lento.
Ryan Amos
Ordenar externa @ryan es en la mayoría de los casos una combinación de clase así que en la última combinación que puede hacer el cheque :)
monstruo de trinquete
Si los datos están en el disco, deberán cargarse en la memoria. Esto sucede automáticamente por el sistema de archivos. Si tenemos que encontrar un número (el problema no indica lo contrario), entonces leer el archivo ordenado línea por línea es el método más eficiente. Utiliza poca memoria y no es más lento que cualquier otra cosa: el archivo debe leerse.
Tony Ennis
¿Cómo ordenarás 4 mil millones de enteros cuando solo tienes 1 GB de memoria? Si usa la memoria virtual, llevará mucho tiempo ya que los bloques de memoria entran y salen de la memoria física.
Klas Lindbäck
44
@klas merge sort está diseñado para eso
ratchet freak
2

Si no asume la restricción de 32 bits, simplemente devuelva un número de 64 bits generado aleatoriamente (o 128 bits si es un pesimista). La posibilidad de colisión es 1 in 2^64/(4*10^9) = 4611686018.4(aproximadamente 1 en 4 mil millones). ¡Tendrías razón la mayor parte del tiempo!

(Bromeando ... más o menos)

Peter Gibson
fuente
Veo que esto ya se ha sugerido :) votos a favor para esas personas
Peter Gibson
La paradoja del cumpleaños hace que este tipo de solución no valga la pena el riesgo, sin consultar el archivo para ver si su suposición aleatoria fue realmente una respuesta válida. (Cumpleaños paradoja no se aplica en este caso, pero en repetidas ocasiones llamar a esta función para generar nuevos valores únicos no crear una situación paradoja del cumpleaños.)
Peter Cordes
@PeterCordes Los números de 128 bits generados aleatoriamente son precisamente cómo funcionan los UUID: incluso mencionan la paradoja del cumpleaños al calcular la probabilidad de una colisión en la página del UUID de
Peter Gibson,
Variante: Encuentra el máximo en el set, suma 1.
Phil
Clasificaría rápidamente la matriz original (sin almacenamiento adicional), luego marcharía a través de la matriz e informaría el primer entero 'omitido'. Hecho. Respondió la pregunta.
Nivel 42