2010-06-09 24 views
16

En una secuencia de longitud n, donde n = 2k + 3, es decir, hay k números únicos aparecieron dos veces y tres números aparecieron solo una vez.Buscar tres números apareció una sola vez

La pregunta es: cómo encontrar los tres números únicos que aparecieron solo una vez?

por ejemplo, en la secuencia 1 1 2 6 3 6 5 7 7 los tres números únicos son 2 3 5.

Nota: = n < 1E6 y el número estará en el intervalo de 1 a 2E9

Límites de memoria: 1000 KB, esto implica que no podemos almacenar toda la secuencia.

método que he intentado (límite de memoria excede):

Me inicializar un árbol, y cuando se lee en un número trato de sacarlo del árbol, si el quitar devuelve falso (no encontrado) , Lo agrego al árbol. Finalmente, el árbol tiene los tres números. Funciona, pero se excede el límite de memoria.

Sé cómo encontrar uno o dos de esos números utilizando la manipulación de bits. Entonces me pregunto si

podemos encontrar tres utilizando el mismo método (¿o algún método similar)?

método para encontrar uno/dos número (s) apareció sólo una vez:

Si hay un número apareció sólo una vez, podemos aplicar XOR con la secuencia de encontrarlo.

Si hay dos, podemos primero aplicar XOR a la secuencia, luego separar la secuencia en 2 partes por un bit del resultado que es 1, y aplicar XOR a las 2 partes, y encontraremos el responder.

+5

Pregunta esotérica + sin propósito útil = ¿deberes? –

+1

@Robert Harvey - Podría ser una pregunta de Project Euler –

+0

¿Cuántas pases sobre los datos está permitido? De lo contrario, parecería que hay una solución O (n^2) trivial que usa igualdad (el operador de igualdad es bitwise). – Akusete

Respuesta

7

Puede hacerlo de forma similar a los casos más simples de uno y dos valores diferentes.

Necesitamos dos enteros por cada bit de los números (por ejemplo, 32 bits). Para cada número, si ese bit es cero, XOR es el primer entero con él. Si no es así, XOR es el segundo entero con él.

Además, tenga en cuenta la cantidad de veces que encuentra un 1 o 0 en cada posición (solo necesitamos verificar si esto es par o impar, así que mantenga un valor booleano).

Después de iterar, nuestros pares de números enteros serán uno de los siguientes. El primer número aquí representa un conteo par, el segundo es impar.

0, a^b^c 
a^b, c 
a^c, b 
b^c, a 

Para cada par, verifique el entero de conteo par. Si es cero, entonces sabemos que el otro entero es a^b^c, ya que ninguno de nuestros resultados será igual. De lo contrario, hemos encontrado un valor en el número entero impar.

public static int[] find3(int[] list) { 
    int[][] xors = new int[32][2]; 
    boolean[] counts = new boolean[32]; 
    for (int curr : list) { 
     for (int i = 0; i < 32; i++) { 
      xors[i][(curr & (1 << i)) >> i] ^= curr; 
      counts[i] ^= ((curr & (1 << i)) == (1 << i)); 
     } 
    } 

    // this really shouldn't take so many lines 
    int[] ret = new int[3]; 
    int found = 0; 
    for (int i = 0; i < 32; i++) { 
     int oddCount = xors[i][counts[i] ? 1 : 0]; 
     int evenCount = xors[i][counts[i] ? 0 : 1]; 
     if (evenCount != 0) { // avoid the 0, a^b^c case. 
      if (found == 0) { 
       ret[0] = oddCount;// a 
       ret[2] = evenCount;// b^c for now 
       found++; 
      } else if (found == 1 && ret[0] != oddCount) { 
       ret[1] = oddCount;// b 
       ret[2] ^= oddCount;// (b^c)^b == c 
       break; 
      } 
     } 
    } 
    return ret; 
} 
+1

¡Impresionante! Lo resuelves perfectamente Gracias por tu awser. – shilk

+0

@shilk. Estoy de acuerdo. Buena solución para los límites establecidos. –

7

Esta es una pregunta clásica: en realidad, hace unas semanas solo me la preguntaron. Para resolverlo, tome la cantidad de números distintos posibles que podrían aparecer y asigne esos muchos bits.

Por ejemplo, si los números de la lista deben ser de entre 1-20, se asignan 20 bits - una para cada número, y se inicializa cada bit como 0.

A continuación, recorrer la lista. Cada vez que vea un número, voltee el bit correspondiente.

Por ejemplo: con su lista de ejemplos de 2 6 3 6 5 7 7, podríamos asignar 7 bits (para 1 2 3 4 5 6 7). A continuación, a medida que atravesamos la lista, haríamos lo siguiente:

  • flip segundo poco
  • flip sexto bits
  • flip tercera bits
  • flip sexto bits
  • etc

Luego, una vez que haya terminado de recorrer la lista, puede leer los bits para encontrar los tres números únicos. Todos ellos estarán representados por '1' bits, y los otros números estarán representados por 0s.

Lea la lista dos veces, lo que lleva 2n tiempo, que es O (n).


Editar: Es posible que los límites no estén dados. Una solución, entonces, es simplemente leer la lista primero para determinar los límites usted mismo, entonces sigue siendo O (n).

Sin embargo, un problema que podría ocurrir es que la lista podría ser muy pequeña, pero algunos números muy grandes, lo que hace que el rango sea demasiado grande. Por ejemplo:

1, 99999999999999999, 1, 99999999999999999, 2, 3, 4 

la solución de ese problema requeriría una gran cantidad de memoria debido al gran número en la lista, porque a pesar de que hay muy pocos números, el rango es muy grande y estamos asignando bits de acuerdo con el rango.

La solución podría entonces ser ajustada para dar una nueva solución de la siguiente manera utilizando una tabla hash (aunque no estoy seguro de si esto está permitido dada "la manipulación de bits única" del problema estipulación):

  1. Deje L denotan la lista original, y C denotan una copia de la misma.
  2. Elimine todos los duplicados de C (existen numerosas maneras de hacerlo de manera eficiente).
  3. Crear una tabla hash H, y para cada elemento en C, inserte un par clave/valor < number, pos> en H donde number es el elemento actual de C y pos es su posición en C. Entonces, dado un número que aparece en L, ahora podemos usar H para encontrar la posición de ese número en C.
  4. asignar un número de bits igual al tamaño de C, e inicializar los bits a 0.
  5. Traverse L. Cada vez que corremos a través de un número, obtenemos su valor de H, y volteamos ese bit en nuestra lista de bits.
  6. Recorre la lista de bits - para cada '1' bit, obtén el número de C que está en esa posición, es decir, uno de los números únicos.
+1

¿La pregunta original mencionó los números limitados? – Akusete

+0

No, pero puede determinar los límites para una lista dada al iterar por la lista una vez encontrar los valores más bajos/más altos en él. Actualizaré mi respuesta en consecuencia. – Cam

+1

Gracias por su respuesta. He pensado en este método antes. Pero el rango de números es 1-2e9, yn es 3-1e6. Por lo tanto, este método no funcionará. – shilk

6

Si una solución probabilística será suficiente, entonces podría usar un Bloom Filter.

Crea dos filtros Bloom. El primero (A) contiene números que se han encontrado al menos uno, y el segundo (B) contiene números que se han encontrado dos veces.

Pseudocódigo:

A = empty 
B = empty 

foreach x in the list 
    if x in A 
    add x to B 
    else 
    add x to A 

foreach x in the list 
    if x in A 
    if !(x in B) 
     print x 

Si utiliza la plena 1000KB entonces la probabilidad de error sería ridículamente bajo.

+0

¿Cómo se puede recorrer la lista dos veces ya que no tenemos suficiente memoria para almacenar toda la lista? No creo que Bloom Filter funcione en esta situación. – shilk

+0

@shilk: un filtro de floración es una matriz de bits glorificada, por lo que es extremadamente compacto. Usted "agrega" elementos al filtro bloom ajustando bits en el índice 'hashcode% array.length' en 1 para varias funciones hash diferentes, y prueba la membresía establecida de una manera similar. Esta es una solución probabilística perfectamente adecuada para su pregunta. – Juliet

+0

@Juliet, aunque tiene razón sobre el segundo cruce. No puede usar el filtro Bloom para volver a recorrer los elementos y, al mismo tiempo, no podemos almacenar los elementos: -/- Perdí ese bit. –

1

El problema se vuelve más y más difícil a medida que agrega valores únicos, principalmente porque puede elegir A, B, C tal que A xor B xor C = 0. Se hace más y más difícil detectar si un subconjunto de los valores tiene la misma suma de comprobación porque contiene todos los valores únicos, o porque omite valores que pasaron a xor a 0.

Puede hacer 3 valores en espacio constante y O (n * k) tiempo, donde k es el número de bits en el entero más grande. (Entonces O (n) tiempo para su caso típico: enteros de 32 bits.)

Sería interesante averiguar si el límite de tiempo se vuelve no lineal en N a medida que aumenta el número de valores únicos y continúa requiere espacio constante.

//Special check for 0, because otherwise we don't know A xor B xor C != A xor B 
if items unique-contains 0 then 
    return 0 ++ SubProblem2Unique(items - 0) 
//Compute A xor B xor C 
val x = fold xor items 
//Try to find a split which separates A and B from C. 
for i in 0..WORD_SIZE 
    //see if the checksum splits 
    val x1 = fold xor [e in items where e & (1<<i) == 0] 
    val x2 = x xor x1 
    if x1 == x or x2 == x then continue //ith bit was the same for A and B and C 
    //C is either x1 or x2 
    val C = if items unique-contains x1 then x1 else x2 
    return C++ SubProblem2Unique(items - C) 

throw InvalidInput 
9

Para una versión más general de este problema (sin esos límites tontas):

Usted puede hacer esto en el tiempo O (n) y O (1) espacio sin asumir ninguna límites, o iterando sobre todos los bits, y usando solo O (1) trucos de manipulación de bit de tiempo como el truco de XOR que funcionó para 2 números faltantes.

Aquí es código (pseudo) para encontrar sólo uno de los números:

// Given an array arr with 2k+3 numbers, k of which are repeated twice 
// and the remaining three are distinct: a,b,c. 
// returns one of a,b,c. 
int FindUnique(int []arr) { 

    int s = 0; // This will ultimately hold a^b^c (bitwise XOR) 

    for (int i = 0; i < arr.Length; i++) { 
     s ^= arr[i]; 
    } 

    int d = 0; // this holds diff(a,s)^diff(b,s)^diff(c,s) 

    for (int i = 0; i < arr.Length; i++) { 
     d ^= diff(arr[i],s); 
    } 

    int e = lowestBit(d); // This gives the position where one of a,b,c differs 
          // from the others. 

    int bucket1 = 0; 
    int bucket2 = 0; 

    for (int i = 0; i < arr.Length; i++) { 
     if (arr[i] & e) { 
      bucket1 ^= arr[i]; 
     } else { 
      bucket2 ^= arr[i]; 
     } 
    } 

    int count1 = 0; 
    int count2 = 0; 

    for (int i = 0; i < arr.Length; i++) { 
     if (arr[i] == bucket1) { 
      count1++; 
     } 

     if (arr[i] == bucket2) { 
      count2++; 
     } 
    } 

    if (count1 == 1) return bucket1; 

    return bucket2; 
} 

// return a number with the lowest bit of x^s set to 1 and rest 0. 
// i.e. the lowest bit position where x and s differ. 
int diff(int x, int s) { 
    return lowestBit(x^s); 
} 

// Returns a number with only the lowest bit of y set. 
int lowestBit(int y) { 
    return y & ~(y-1); 
} 

La idea es la siguiente:

que dicen los números que aparecen una vez que son a, b, c.

Ahora ejecute el XOR a través de la matriz para obtener s = a XOR b XOR c.

Dado que los números son distintos, observe que s no puede ser ni a ni boc (ya que los otros dos serán iguales en ese momento), por lo que hay al menos un bit (no necesariamente en la misma posición), donde cada de a, b, c difiere de s.

En el caso de los dos números, pudimos ver que s es distinto de cero y elegimos un bit que diferenciaba un & b y trabajamos con eso.

Nos encontramos con dificultades cuando tenemos tres números, pero aún podemos encontrar un poco para diferenciar uno de los números.

Para cada número x, encuentre el bit más bajo que difiera de s. Considere el número binario en el que solo ese bit se establece en uno y el resto es cero. Llame a este número diff (x).

Ahora si calculamos diff (x) para cada número y XOR juntos, obtenemos d = diff (a) XOR diff (b) XOR diff (c).

Observe que d no puede ser cero.

Ahora encuentre el bit más bajo de d. Esta posición de bit se puede usar para dividir uno de a, b, c, ya que no todos a, b, c pueden tener el mismo bit en esa posición: si lo hicieron, entonces ese bit de s que es el XOR de aquellos tres deben ser iguales, pero nos aseguramos de que elegimos ese bit de s para que difiera de al menos uno de los bits correspondientes en a, b, c.

Volvemos a XOR, diferenciando en este bit, y verificando cuál de los dos números resultantes aparece exactamente una vez en la matriz. Una vez que encontramos un número, sabemos cómo lidiar con dos números.

Para encontrar el diff solo use el bithack: x & ~(x-1), que es un bit-hack estándar y puede considerarse O (1) (en lugar de O (número de bits)).

0

¿Por qué no utilizar un hashset? - Si ya existe un número, elimínelo del hashset - si no existe un número, colóquelo en hashset El hashset final contiene solo números únicos. Hora: O (n) Memoria: o (k) donde k es el número de elementos distintos.

Con enfoque hashset, la solución es escalable y se puede utilizar para determinar cualquier cantidad de elementos únicos en cualquier secuencia dada.

+0

Porque no puede ajustar medio millón de valores de 32 bits en un hashset en 1000 KB. –

Cuestiones relacionadas