sheet rap for examples dummies complexity cheat big best asymptotic algorithm big-o

algorithm - rap - Algoritmo O(nlogn)-Encuentra tres espaciados uniformemente dentro de una cadena binaria



big o notation java (30)

Ayer tuve esta pregunta en una prueba de Algoritmos, y no puedo encontrar la respuesta. Me está volviendo loco, porque valió aproximadamente 40 puntos. Me imagino que la mayoría de la clase no lo resolvió correctamente, porque no he encontrado una solución en las últimas 24 horas.

Dada una cadena binaria arbitraria de longitud n, encuentre tres espaciados uniformemente dentro de la cadena, si es que existen. Escriba un algoritmo que resuelva esto en O (n * log (n)) tiempo.

Entonces, cadenas como estas tienen tres que están "espaciadas uniformemente": 11100000, 0100100100

editar: es un número aleatorio, por lo que debería poder funcionar para cualquier número. Los ejemplos que di fueron para ilustrar la propiedad "espaciada uniformemente". Entonces 1001011 es un número válido. Con 1, 4 y 7 siendo los que están espaciados de manera uniforme.


¡Finalmente! Siguiendo las pistas en la respuesta de sdcvvc , lo tenemos: ¡el algoritmo O (n log n) para el problema! Es simple también, después de que lo entiendas. Aquellos que adivinaron FFT tenían razón.

El problema: nos da una cadena binaria S de longitud n , y queremos encontrar tres 1s espaciados uniformemente en ella. Por ejemplo, S puede ser 110110010 , donde n = 9. Ha espaciado uniformemente 1s en las posiciones 2, 5 y 8.

  1. Escanee S izquierda a derecha, y haga una lista L de posiciones de 1. Para el S=110110010 anterior, tenemos la lista L = [1, 2, 4, 5, 8]. Este paso es O (n). El problema ahora es encontrar una progresión aritmética de longitud 3 en L , es decir, encontrar distintas a, b, c en L tales que ba = cb , o equivalentemente a + c = 2b . Para el ejemplo anterior, queremos encontrar la progresión (2, 5, 8).

  2. Haz un polinomio p con términos x k para cada k en L Para el ejemplo anterior, hacemos el polinomio p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Este paso es O (n).

  3. Encuentre el polinomio q = p 2 , usando la Transformada Rápida de Fourier . Para el ejemplo anterior, obtenemos el polinomio q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2 . Este paso es O (n log n).

  4. Ignora todos los términos, excepto los correspondientes a x 2k para algunos k en L Para el ejemplo anterior, obtenemos los términos x 16 , 3x 10 , x 8 , x 4 , x 2 . Este paso es O (n), si elige hacerlo en absoluto.

Aquí está el punto crucial: el coeficiente de cualquier x 2b para b en L es precisamente el número de pares (a, c) en L tal que a + c = 2b . [CLRS, Ex. 30.1-7] Uno de estos pares es (b, b) siempre (por lo que el coeficiente es al menos 1), pero si existe algún otro par (a, c) , entonces el coeficiente es al menos 3, de (a, c ) y (c, a) . Para el ejemplo anterior, tenemos el coeficiente de x 10 para ser 3 precisamente debido a la AP (2,5,8). (Estos coeficientes x 2b siempre serán números impares, por las razones anteriores, y todos los demás coeficientes en q siempre serán pares).

Entonces, el algoritmo es mirar los coeficientes de estos términos x 2b , y ver si alguno de ellos es mayor que 1. Si no hay ninguno, entonces no hay 1s espaciados uniformemente. Si hay un b en L para el cual el coeficiente de x 2b es mayor que 1, entonces sabemos que existe algún par (a, c) - distinto de (b, b) - para el cual a + c = 2b . Para encontrar el par real, simplemente probamos cada a en L (la c correspondiente sería 2b-a ) y veremos si hay 1 en la posición 2b-a en S Este paso es O (n).

Eso es todo amigos.

Uno podría preguntarse: ¿necesitamos usar FFT? Muchas respuestas, como beta''s , flybywire''s y rsp''s , sugieren que el enfoque que verifica cada par de 1 y ve si hay un 1 en la "tercera" posición, podría funcionar en O (n log n), basado en la intuición. que si hay demasiados 1s, encontraríamos un triple fácilmente, y si hay muy pocos 1s, comprobar todos los pares lleva poco tiempo. Desafortunadamente, aunque esta intuición es correcta y el enfoque simple es mejor que O (n 2 ), no es significativamente mejor. Como en la respuesta de sdcvvc , podemos tomar el "conjunto tipo Cantor" de cadenas de longitud n = 3 k , con 1s en las posiciones cuya representación ternaria tiene solo 0s y 2s (no 1s) en ella. Tal cadena tiene 2 k = n (log 2) / (log 3) ≈ n 0.63 en ella y no 1s espaciados uniformemente, por lo que el control de todos los pares sería del orden del cuadrado de la cantidad de 1s en él: eso es 4 k ≈ n 1.26 que desafortunadamente es asintóticamente mucho más grande que (n log n). De hecho, el peor de los casos es aún peor: Leo Moser en 1953 constructed (efectivamente) cadenas de caracteres que tienen n 1-c / √ (log n) 1s en ellas, pero no 1s espaciado uniformemente, lo que significa que en tales cadenas, el simple el enfoque tomaría Θ (n 2-2c / √ (log n) ) - ¡solo un poquito mejor que Θ (n 2 ) , sorprendentemente!

Aproximadamente el número máximo de 1s en una cadena de longitud n sin 3 espaciados uniformemente (lo que vimos arriba fue al menos n 0,63 desde la construcción fácil tipo Cantor, y al menos n 1-c / √ (log n) con Construcción de Moser) - esto es OEIS A003002 . También se puede calcular directamente a partir del OEIS A065825 como el k tal que A065825 (k) ≤ n <A065825 (k + 1). Escribí un programa para encontrarlos, y resulta que el algoritmo codicioso no da la cadena más larga. Por ejemplo, para n = 9, podemos obtener 5 1s (110100011) pero el codicioso da solo 4 (110110000), para n = 26 podemos obtener 11 1s (11001010001000010110001101) pero el codicioso da solo 8 (11011000011011000000000000), y para n = 74 podemos obtener 22 1s (11000010110001000001011010001000000000000000010001011010000010001101000011) pero el codicioso solo da 16 (11011000011011000000000000011011000011011000000000000000000000000000000000). Sin embargo, acordaron en bastantes lugares hasta 50 (por ejemplo, de 38 a 50). Como dicen las referencias de OEIS, parece que Jaroslaw Wroblewski está interesado en esta cuestión y mantiene un sitio web sobre estos conjuntos que no promedian . Los números exactos son conocidos solo hasta 194.


Esta no es una solución, sino una línea de pensamiento similar a lo que Olexiy estaba pensando

Estaba jugando con la creación de secuencias con un número máximo de unidades, y todas son bastante interesantes, obtuve hasta 125 dígitos y aquí están los primeros 3 números que encontró al intentar insertar tantos ''1'' bits como sea posible:

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 10110100010110100000000000010110100010110100000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 10011001010011001000000000010011001010011001000000000000000000000000000000000000010011001010011001000000000010011001010011001

Tenga en cuenta que son todos fractales (lo cual no es demasiado sorprendente dadas las limitaciones). Puede haber algo en el pensamiento hacia atrás, quizás si la cuerda no es un fractal con una característica, entonces debe tener un patrón de repetición.

Gracias a beta por el mejor término para describir estos números.

Actualización: ¡Ay !, parece que el patrón se rompe cuando se comienza con una cadena inicial lo suficientemente grande, como por ejemplo: 10000000000001:

100000000000011 10000000000001101 100000000000011011 10000000000001101100001 100000000000011011000011 10000000000001101100001101 100000000000011011000011010000000001 100000000000011011000011010000000001001 1000000000000110110000110100000000010011 1000000000000110110000110100000000010011001 10000000000001101100001101000000000100110010000000001 10000000000001101100001101000000000100110010000000001000001 1000000000000110110000110100000000010011001000000000100000100000000000001 10000000000001101100001101000000000100110010000000001000001000000000000011 1000000000000110110000110100000000010011001000000000100000100000000000001101 100000000000011011000011010000000001001100100000000010000010000000000000110100001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001 100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001 1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011 10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001


Revisión: 2009-10-17 23:00

He corrido esto en grandes cantidades (como, cadenas de 20 millones) y ahora creo que este algoritmo no es O (n logn). A pesar de eso, es una implementación suficientemente buena y contiene varias optimizaciones que la hacen funcionar muy rápido. Evalúa todas las disposiciones de cadenas binarias de 24 o menos dígitos en menos de 25 segundos.

Actualicé el código para incluir la observación 0 <= L < M < U <= X-1 de hoy.

Original

Esto es, en concepto, similar a otra pregunta que respondí . Ese código también observó tres valores en una serie y determinó si un triplete cumplía una condición. Aquí está el código C # adaptado de eso:

using System; using System.Collections.Generic; namespace 1560523 { class Program { public struct Pair<T> { public T Low, High; } static bool FindCandidate(int candidate, List<int> arr, List<int> pool, Pair<int> pair, ref int iterations) { int lower = pair.Low, upper = pair.High; while ((lower >= 0) && (upper < pool.Count)) { int lowRange = candidate - arr[pool[lower]]; int highRange = arr[pool[upper]] - candidate; iterations++; if (lowRange < highRange) lower -= 1; else if (lowRange > highRange) upper += 1; else return true; } return false; } static List<int> BuildOnesArray(string s) { List<int> arr = new List<int>(); for (int i = 0; i < s.Length; i++) if (s[i] == ''1'') arr.Add(i); return arr; } static void BuildIndexes(List<int> arr, ref List<int> even, ref List<int> odd, ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex) { for (int i = 0; i < arr.Count; i++) { bool isEven = (arr[i] & 1) == 0; if (isEven) { evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1}); oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count}); even.Add(i); } else { oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1}); evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count}); odd.Add(i); } } } static int FindSpacedOnes(string s) { // List of indexes of 1s in the string List<int> arr = BuildOnesArray(s); //if (s.Length < 3) // return 0; // List of indexes to odd indexes in arr List<int> odd = new List<int>(), even = new List<int>(); // evenIndex has indexes into arr to bracket even numbers // oddIndex has indexes into arr to bracket odd numbers List<Pair<int>> evenIndex = new List<Pair<int>>(), oddIndex = new List<Pair<int>>(); BuildIndexes(arr, ref even, ref odd, ref evenIndex, ref oddIndex); int iterations = 0; for (int i = 1; i < arr.Count-1; i++) { int target = arr[i]; bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || FindCandidate(target, arr, even, evenIndex[i], ref iterations); if (found) return iterations; } return iterations; } static IEnumerable<string> PowerSet(int n) { for (long i = (1L << (n-1)); i < (1L << n); i++) { yield return Convert.ToString(i, 2).PadLeft(n, ''0''); } } static void Main(string[] args) { for (int i = 5; i < 64; i++) { int c = 0; string hardest_string = ""; foreach (string s in PowerSet(i)) { int cost = find_spaced_ones(s); if (cost > c) { hardest_string = s; c = cost; Console.Write("{0} {1} {2}/r", i, c, hardest_string); } } Console.WriteLine("{0} {1} {2}", i, c, hardest_string); } } } }

Las principales diferencias son:

  1. Exhaustiva búsqueda de soluciones
    Este código genera un conjunto de datos de potencia para encontrar la entrada más difícil de resolver para este algoritmo.
  2. Todas las soluciones versus las más difíciles de resolver
    El código para la pregunta anterior generó todas las soluciones usando un generador de pitón. Este código solo muestra el más difícil para cada longitud de patrón.
  3. Algoritmo de puntuación
    Este código verifica la distancia desde el elemento central a su borde izquierdo y derecho. El código python probó si una suma estaba por encima o por debajo de 0.
  4. Convergencia en un candidato
    El código actual funciona desde el medio hacia el borde para encontrar un candidato. El código en el problema anterior funcionó desde los bordes hacia el centro. Este último cambio proporciona una gran mejora en el rendimiento.
  5. Uso de piscinas pares e impares
    En base a las observaciones al final de este informe, el código busca pares de números pares de pares de números impares para encontrar L y U, manteniendo M fijo. Esto reduce el número de búsquedas al precomputar información. En consecuencia, el código utiliza dos niveles de indirección en el bucle principal de FindCandidate y requiere dos llamadas a FindCandidate para cada elemento intermedio: una para los números pares y otra para los impares.

La idea general es trabajar en índices, no en la representación cruda de los datos. El cálculo de una matriz donde aparecen los 1 permite que el algoritmo se ejecute en el tiempo proporcional al número de 1 en los datos en lugar de en el tiempo proporcional a la longitud de los datos. Esta es una transformación estándar: cree una estructura de datos que permita una operación más rápida manteniendo el problema equivalente.

Los resultados están desactualizados: eliminados.

Edición: 2009-10-16 18:48

En los datos de yx, a los que se da cierta credibilidad en las otras respuestas como representativas de los datos duros para calcular, obtengo estos resultados ... Eliminé estos. Están desactualizados.

Me gustaría señalar que esta información no es la más difícil para mi algoritmo, por lo que creo que la suposición de que los fractales de yx son los más difíciles de resolver está equivocada. El peor caso para un algoritmo en particular, espero, dependerá del algoritmo en sí mismo y probablemente no sea consistente en diferentes algoritmos.

Edición: 2009-10-17 13:30

Otras observaciones sobre esto.

Primero, convierta la cadena de 0 y 1 en una matriz de índices para cada posición de los 1. Digamos que la longitud de esa matriz A es X. Entonces, el objetivo es encontrar

0 <= L < M < U <= X-1

tal que

A[M] - A[L] = A[U] - A[M]

o

2*A[M] = A[L] + A[U]

Como A [L] y A [U] suman un número par, no pueden ser (par, impar) o (impar, par). The search for a match could be improved by splitting A[] into odd and even pools and searching for matches on A[M] in the pools of odd and even candidates in turn.

However, this is more of a performance optimization than an algorithmic improvement, I think. The number of comparisons should drop, but the order of the algorithm should be the same.

Edit 2009-10-18 00:45

Yet another optimization occurs to me, in the same vein as separating the candidates into even and odd. Since the three indexes have to add to a multiple of 3 (a, a+x, a+2x -- mod 3 is 0, regardless of a and x), you can separate L, M, and U into their mod 3 values:

M L U 0 0 0 1 2 2 1 1 0 2 1 1 2 0 2 0 1 1 0 2 2

In fact, you could combine this with the even/odd observation and separate them into their mod 6 values:

M L U 0 0 0 1 5 2 4 3 3 4 2 5 1

y así. This would provide a further performance optimization but not an algorithmic speedup.


Sospecho que un enfoque simple que se parece a O (n ^ 2) realmente rendirá algo mejor, como O (n ln (n)). Las secuencias que tardan más en probarse (para cualquier n dada) son las que no contienen tríos, y eso impone severas restricciones sobre el número de 1 que puede haber en la secuencia.

He llegado a algunos argumentos que agitan la mano, pero no he podido encontrar una prueba ordenada. Voy a dar una puñalada en la oscuridad: la respuesta es una idea muy inteligente que el profesor ha sabido durante tanto tiempo que parece ser obvio, pero es demasiado difícil para los estudiantes. (O eso o dormiste durante la conferencia que lo cubrió).


Su problema se llama AVERAGE en este documento (1999):

Un problema es 3SUM-hard si hay una reducción sub-cuadrática del problema 3SUM: Dado un conjunto A de n enteros, ¿hay elementos a, b, c en A tales que a + b + c = 0? No se sabe si AVERAGE tiene 3SUM-hard. Sin embargo, hay una reducción simple de tiempo lineal de AVERAGE a 3SUM, cuya descripción omitimos.

Wikipedia :

Cuando los enteros están en el rango [-u ... u], 3SUM se puede resolver en el tiempo O (n + u lg u) representando S como un vector de bits y realizando una convolución usando FFT.

Esto es suficiente para resolver su problema :).

Lo que es muy importante es que O (n log n) es la complejidad en términos de número de ceros y unos, no el recuento de unos (que podría darse como una matriz, como [1,5,9,15]). Comprobar si un conjunto tiene una progresión aritmética, términos de número de 1, es difícil, y de acuerdo con ese documento a partir de 1999 no se conoce un algoritmo más rápido que O (n 2 ), y se conjetura que no existe. Todos los que no toman esto en cuenta intentan resolver un problema abierto.

Otra información interesante, principalmente irrevelante:

Límite inferior:

Un límite inferior fácil es un conjunto tipo Cantor (números 1..3 ^ n-1 que no contiene 1 en su expansión ternaria) - su densidad es n ^ (log_3 2) (alrededor de 0.631). De modo que cualquier comprobación de si el conjunto no es demasiado grande y luego verificar todos los pares no es suficiente para obtener O (n log n). Tienes que investigar la secuencia de manera más inteligente. here se cita un límite inferior mejor; es n 1-c / (log (n)) ^ (1/2) . Esto significa que el conjunto de Cantor no es óptimo.

Límite superior: mi viejo algoritmo:

Se sabe que para n grande, un subconjunto de {1,2, ..., n} que no contiene progresión aritmética tiene como máximo n / (log n) ^ (1/20) elementos. El documento On triples en la progresión aritmética es más: el conjunto no puede contener más de n * 2 28 * (log log n / log n) 1/2 elementos. Para que pueda verificar si se logra ese límite y si no, intente ingenuamente los pares. Este es el algoritmo O (n 2 * log log n / log n), más rápido que O (n 2 ). Desafortunadamente "En triples ..." está en Springer, pero la primera página está disponible, y la exposición de Ben Green está disponible here , página 28, teorema 24.

Por cierto, los documentos son de 1999, el mismo año que el primero que mencioné, por lo que es probable que el primero no mencione ese resultado.


A fun question, but once you realise that the actual pattern between two ''1''s does not matter, the algorithm becomes:

  • scan look for a ''1''
  • starting from the next position scan for another ''1'' (to the end of the array minus the distance from the current first ''1'' or else the 3rd ''1'' would be out of bounds)
  • if at the position of the 2nd ''1'' plus the distance to the first 1'' a third ''1'' is found, we have evenly spaces ones.

In code, JTest fashion, (Note this code isn''t written to be most efficient and I added some println''s to see what happens.)

import java.util.Random; import junit.framework.TestCase; public class AlgorithmTest extends TestCase { /** * Constructor for GetNumberTest. * * @param name The test''s name. */ public AlgorithmTest(String name) { super(name); } /** * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); } /** * @see TestCase#tearDown() */ protected void tearDown() throws Exception { super.tearDown(); } /** * Tests the algorithm. */ public void testEvenlySpacedOnes() { assertFalse(isEvenlySpaced(1)); assertFalse(isEvenlySpaced(0x058003)); assertTrue(isEvenlySpaced(0x07001)); assertTrue(isEvenlySpaced(0x01007)); assertTrue(isEvenlySpaced(0x101010)); // some fun tests Random random = new Random(); isEvenlySpaced(random.nextLong()); isEvenlySpaced(random.nextLong()); isEvenlySpaced(random.nextLong()); } /** * @param testBits */ private boolean isEvenlySpaced(long testBits) { String testString = Long.toBinaryString(testBits); char[] ones = testString.toCharArray(); final char ONE = ''1''; for (int n = 0; n < ones.length - 1; n++) { if (ONE == ones[n]) { for (int m = n + 1; m < ones.length - m + n; m++) { if (ONE == ones[m] && ONE == ones[m + m - n]) { System.out.println(" IS evenly spaced: " + testBits + ''='' + testString); System.out.println(" at: " + n + ", " + m + ", " + (m + m - n)); return true; } } } } System.out.println("NOT evenly spaced: " + testBits + ''='' + testString); return false; } }



Assumption:

Just wrong, talking about log(n) number of upper limit of ones

EDITAR:

Now I found that using Cantor numbers (if correct), density on set is (2/3)^Log_3(n) (what a weird function) and I agree, log(n)/n density is to strong.

If this is upper limit, there is algorhitm who solves this problem in at least O(n*(3/2)^(log(n)/log(3))) time complexity and O((3/2)^(log(n)/log(3))) space complexity. (check Justice''s answer for algorhitm)

This is still by far better than O(n^2)

This function ((3/2)^(log(n)/log(3))) really looks like n*log(n) on first sight.

How did I get this formula?

Applaying Cantors number on string.
Supose that length of string is 3^p == n
At each step in generation of Cantor string you keep 2/3 of prevous number of ones. Apply this p times.

That mean (n * ((2/3)^p)) -> (((3^p)) * ((2/3)^p)) remaining ones and after simplification 2^p. This mean 2^p ones in 3^p string -> (3/2)^p ones . Substitute p=log(n)/log(3) and get
((3/2)^(log(n)/log(3)))


Below is a solution. There could be some little mistakes here and there, but the idea is sound.

Edit: It''s not n * log(n)

PSEUDO CODE:

foreach character in the string if the character equals 1 { if length cache > 0 { //we can skip the first one foreach location in the cache { //last in first out kind of order if ((currentlocation + (currentlocation - location)) < length string) if (string[(currentlocation + (currentlocation - location))] equals 1) return found evenly spaced string else break; } } remember the location of this character in a some sort of cache. } return didn''t find evenly spaced string

C# code:

public static Boolean FindThreeEvenlySpacedOnes(String str) { List<int> cache = new List<int>(); for (var x = 0; x < str.Length; x++) { if (str[x] == ''1'') { if (cache.Count > 0) { for (var i = cache.Count - 1; i > 0; i--) { if ((x + (x - cache[i])) >= str.Length) break; if (str[(x + (x - cache[i]))] == ''1'') return true; } } cache.Add(x); } } return false; }

How it works:

iteration 1: x | 101101001 // the location of this 1 is stored in the cache iteration 2: x | 101101001 iteration 3: a x b | | | 101101001 //we retrieve location a out of the cache and then based on a //we calculate b and check if te string contains a 1 on location b //and of course we store x in the cache because it''s a 1 iteration 4: axb ||| 101101001 a x b | | | 101101001 iteration 5: x | 101101001 iteration 6: a x b | | | 101101001 a x b | | | 101101001 //return found evenly spaced string


Could this be a solution? I'', not sure if it''s O(nlogn) but in my opinion it''s better than O(n²) because the the only way not to find a triple would be a prime number distribution.

There''s room for improvement, the second found 1 could be the next first 1. Also no error checking.

#include <iostream> #include <string> int findIt(std::string toCheck) { for (int i=0; i<toCheck.length(); i++) { if (toCheck[i]==''1'') { std::cout << i << ": " << toCheck[i]; for (int j = i+1; j<toCheck.length(); j++) { if (toCheck[j]==''1'' && toCheck[(i+2*(j-i))] == ''1'') { std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << " found" << std::endl; return 0; } } } } return -1; } int main (int agrc, char* args[]) { std::string toCheck("1001011"); findIt(toCheck); std::cin.get(); return 0; }


For the simple problem type (ie you search three "1" with only (ie zero or more) "0" between it), Its quite simple: You could just split the sequence at every "1" and look for two adjacent subsequences having the same length (the second subsequence not being the last one, of course). Obviously, this can be done in O(n) time.

For the more complex version (ie you search an index i and an gap g >0 such that s[i]==s[i+g]==s[i+2*g]=="1" ), I''m not sure, if there exists an O(n log n) solution, since there are possibly O(n²) triplets having this property (think of a string of all ones, there are approximately n²/2 such triplets). Of course, you are looking for only one of these, but I have currently no idea, how to find it ...


Here are some thoughts that, despite my best efforts, will not seem to wrap themselves up in a bow. Still, they might be a useful starting point for someone''s analysis.

Consider the proposed solution as follows, which is the approach that several folks have suggested, including myself in a prior version of this answer. :)

  1. Trim leading and trailing zeroes.
  2. Scan the string looking for 1''s.
  3. When a 1 is found:
    1. Assume that it is the middle 1 of the solution.
    2. For each prior 1, use its saved position to compute the anticipated position of the final 1.
    3. If the computed position is after the end of the string it cannot be part of the solution, so drop the position from the list of candidates.
    4. Check the solution.
  4. If the solution was not found, add the current 1 to the list of candidates.
  5. Repeat until no more 1''s are found.

Now consider input strings strings like the following, which will not have a solution:

101 101001 1010010001 101001000100001 101001000100001000001

In general, this is the concatenation of k strings of the form j 0''s followed by a 1 for j from zero to k-1.

k=2 101 k=3 101001 k=4 1010010001 k=5 101001000100001 k=6 101001000100001000001

Note that the lengths of the substrings are 1, 2, 3, etc. So, problem size n has substrings of lengths 1 to k such that n = k(k+1)/2.

k=2 n= 3 101 k=3 n= 6 101001 k=4 n=10 1010010001 k=5 n=15 101001000100001 k=6 n=21 101001000100001000001

Note that k also tracks the number of 1''s that we have to consider. Remember that every time we see a 1, we need to consider all the 1''s seen so far. So when we see the second 1, we only consider the first, when we see the third 1, we reconsider the first two, when we see the fourth 1, we need to reconsider the first three, and so on. By the end of the algorithm, we''ve considered k(k-1)/2 pairs of 1''s. Call that p.

k=2 n= 3 p= 1 101 k=3 n= 6 p= 3 101001 k=4 n=10 p= 6 1010010001 k=5 n=15 p=10 101001000100001 k=6 n=21 p=15 101001000100001000001

The relationship between n and p is that n = p + k.

The process of going through the string takes O(n) time. Each time a 1 is encountered, a maximum of (k-1) comparisons are done. Since n = k(k+1)/2, n > k**2, so sqrt(n) > k. This gives us O(n sqrt(n)) or O(n**3/2). Note however that may not be a really tight bound, because the number of comparisons goes from 1 to a maximum of k, it isn''t k the whole time. But I''m not sure how to account for that in the math.

It still isn''t O(n log(n)). Also, I can''t prove those inputs are the worst cases, although I suspect they are. I think a denser packing of 1''s to the front results in an even sparser packing at the end.

Since someone may still find it useful, here''s my code for that solution in Perl:

#!/usr/bin/perl # read input as first argument my $s = $ARGV[0]; # validate the input $s =~ /^[01]+$/ or die "invalid input string/n"; # strip leading and trailing 0''s $s =~ s/^0+//; $s =~ s/0+$//; # prime the position list with the first ''1'' at position 0 my @p = (0); # start at position 1, which is the second character my $i = 1; print "the string is $s/n/n"; while ($i < length($s)) { if (substr($s, $i, 1) eq ''1'') { print "found ''1'' at position $i/n"; my @t = (); # assuming this is the middle ''1'', go through the positions # of all the prior ''1''s and check whether there''s another ''1'' # in the correct position after this ''1'' to make a solution while (scalar @p) { # $p is the position of the prior ''1'' my $p = shift @p; # $j is the corresponding position for the following ''1'' my $j = 2 * $i - $p; # if $j is off the end of the string then we don''t need to # check $p anymore next if ($j >= length($s)); print "checking positions $p, $i, $j/n"; if (substr($s, $j, 1) eq ''1'') { print "/nsolution found at positions $p, $i, $j/n"; exit 0; } # if $j isn''t off the end of the string, keep $p for next time push @t, $p; } @p = @t; # add this ''1'' to the list of ''1'' positions push @p, $i; } $i++; } print "/nno solution found/n";


How about a simple O(n) solution, with O(n^2) space? (Uses the assumption that all bitwise operators work in O(1).)

The algorithm basically works in four stages:

Stage 1: For each bit in your original number, find out how far away the ones are, but consider only one direction. (I considered all the bits in the direction of the least significant bit.)

Stage 2: Reverse the order of the bits in the input;

Stage 3: Re-run step 1 on the reversed input.

Stage 4: Compare the results from Stage 1 and Stage 3. If any bits are equally spaced above AND below we must have a hit.

Keep in mind that no step in the above algorithm takes longer than O(n). ^ _ ^

As an added benefit, this algorithm will find ALL equally spaced ones from EVERY number. So for example if you get a result of "0x0005" then there are equally spaced ones at BOTH 1 and 3 units away

I didn''t really try optimizing the code below, but it is compilable C# code that seems to work.

using System; namespace ThreeNumbers { class Program { const int uint32Length = 32; static void Main(string[] args) { Console.Write("Please enter your integer: "); uint input = UInt32.Parse(Console.ReadLine()); uint[] distancesLower = Distances(input); uint[] distancesHigher = Distances(Reverse(input)); PrintHits(input, distancesLower, distancesHigher); } /// <summary> /// Returns an array showing how far the ones away from each bit in the input. Only /// considers ones at lower signifcant bits. Index 0 represents the least significant bit /// in the input. Index 1 represents the second least significant bit in the input and so /// on. If a one is 3 away from the bit in question, then the third least significant bit /// of the value will be sit. /// /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space. /// (Where n is the number of bits in the input.) /// </summary> public static uint[] Distances(uint input) { uint[] distanceToOnes = new uint[uint32Length]; uint result = 0; //Sets how far each bit is from other ones. Going in the direction of LSB to MSB for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex) { distanceToOnes[arrayIndex] = result; result <<= 1; if ((input & bitIndex) != 0) { result |= 1; } } return distanceToOnes; } /// <summary> /// Reverses the bits in the input. /// /// As programmed this algorithm needs O(n) time and O(n) space. /// (Where n is the number of bits in the input.) /// </summary> /// <param name="input"></param> /// <returns></returns> public static uint Reverse(uint input) { uint reversedInput = 0; for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1) { reversedInput <<= 1; reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0); } return reversedInput; } /// <summary> /// Goes through each bit in the input, to check if there are any bits equally far away in /// the distancesLower and distancesHigher /// </summary> public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher) { const int offset = uint32Length - 1; for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex) { //hits checks if any bits are equally spaced away from our current value bool isBitSet = (input & bitIndex) != 0; uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex]; if (isBitSet && (hits != 0)) { Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits)); } } } } }

Someone will probably comment that for any sufficiently large number, bitwise operations cannot be done in O(1). You''d be right. However, I''d conjecture that every solution that uses addition, subtraction, multiplication, or division (which cannot be done by shifting) would also have that problem.


I assume the reason this is nlog(n) is due to the following:

  • To find the 1 that is the start of the triplet, you need to check (n-2) characters. If you haven''t found it by that point, you won''t (chars n-1 and n cannot start a triplet) (O(n))
  • To find the second 1 that is the part of the triplet (started by the first one), you need to check m/2 (m=nx, where x is the offset of the first 1) characters. This is because, if you haven''t found the second 1 by the time you''re halfway from the first one to the end, you won''t... since the third 1 must be exactly the same distance past the second. (O(log(n)))
  • It O(1) to find the last 1 since you know the index it must be at by the time you find the first and second.

So, you have n, log(n), and 1... O(nlogn)

Edit: Oops, my bad. My brain had it set that n/2 was logn... which it obviously isn''t (doubling the number on items still doubles the number of iterations on the inner loop). This is still at n^2, not solving the problem. Well, at least I got to write some code :)

Implementation in Tcl

proc get-triplet {input} { for {set first 0} {$first < [string length $input]-2} {incr first} { if {[string index $input $first] != 1} { continue } set start [expr {$first + 1}] set end [expr {1+ $first + (([string length $input] - $first) /2)}] for {set second $start} {$second < $end} {incr second} { if {[string index $input $second] != 1} { continue } set last [expr {($second - $first) + $second}] if {[string index $input $last] == 1} { return [list $first $second $last] } } } return {} } get-triplet 10101 ;# 0 2 4 get-triplet 10111 ;# 0 2 4 get-triplet 11100000 ;# 0 1 2 get-triplet 0100100100 ;# 1 4 7


I came up with something like this:

def IsSymetric(number): number = number.strip(''0'') if len(number) < 3: return False if len(number) % 2 == 0: return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2]) else: if number[len(number)//2] == ''1'': return True return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:]) return False

This is inspired by andycjw.

  1. Truncate the zeros.
  2. If even then test two substring 0 - (len-2) (skip last character) and from 1 - (len-1) (skip the first char)
  3. If not even than if the middle char is one than we have success. Else divide the string in the midle without the midle element and check both parts.

As to the complexity this might be O(nlogn) as in each recursion we are dividing by two.

Espero eso ayude.


I think I have found a way of solving the problem, but I can''t construct a formal proof. The solution I made is written in Java, and it uses a counter ''n'' to count how many list/array accesses it does. So n should be less than or equal to stringLength*log(stringLength) if it is correct. I tried it for the numbers 0 to 2^22, and it works.

It starts by iterating over the input string and making a list of all the indexes which hold a one. This is just O(n).

Then from the list of indexes it picks a firstIndex, and a secondIndex which is greater than the first. These two indexes must hold ones, because they are in the list of indexes. From there the thirdIndex can be calculated. If the inputString[thirdIndex] is a 1 then it halts.

public static int testString(String input){ //n is the number of array/list accesses in the algorithm int n=0; //Put the indices of all the ones into a list, O(n) ArrayList<Integer> ones = new ArrayList<Integer>(); for(int i=0;i<input.length();i++){ if(input.charAt(i)==''1''){ ones.add(i); } } //If less than three ones in list, just stop if(ones.size()<3){ return n; } int firstIndex, secondIndex, thirdIndex; for(int x=0;x<ones.size()-2;x++){ n++; firstIndex = ones.get(x); for(int y=x+1; y<ones.size()-1; y++){ n++; secondIndex = ones.get(y); thirdIndex = secondIndex*2 - firstIndex; if(thirdIndex >= input.length()){ break; } n++; if(input.charAt(thirdIndex) == ''1''){ //This case is satisfied if it has found three evenly spaced ones //System.out.println("This one => " + input); return n; } } } return n;

}

additional note: the counter n is not incremented when it iterates over the input string to construct the list of indexes. This operation is O(n), so it won''t have an effect on the algorithm complexity anyway.


I think this algorithm has O(n log n) complexity (C++, DevStudio 2k5). Now, I don''t know the details of how to analyse an algorithm to determine its complexity, so I have added some metric gathering information to the code. The code counts the number of tests done on the sequence of 1''s and 0''s for any given input (hopefully, I''ve not made a balls of the algorithm). We can compare the actual number of tests against the O value and see if there''s a correlation.

#include <iostream> using namespace std; bool HasEvenBits (string &sequence, int &num_compares) { bool has_even_bits = false; num_compares = 0; for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i) { for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j) { ++num_compares; if (sequence [j] == ''1'' && sequence [j + i] == ''1'' && sequence [j + i * 2] == ''1'') { has_even_bits = true; // we could ''break'' here, but I want to know the worst case scenario so keep going to the end } } } return has_even_bits; } int main () { int count; string input = "111"; for (int i = 3 ; i < 32 ; ++i) { HasEvenBits (input, count); cout << i << ", " << count << endl; input += "0"; } }

This program outputs the number of tests for each string length up to 32 characters. Here''s the results:

n Tests n log (n) ===================== 3 1 1.43 4 2 2.41 5 4 3.49 6 6 4.67 7 9 5.92 8 12 7.22 9 16 8.59 10 20 10.00 11 25 11.46 12 30 12.95 13 36 14.48 14 42 16.05 15 49 17.64 16 56 19.27 17 64 20.92 18 72 22.59 19 81 24.30 20 90 26.02 21 100 27.77 22 110 29.53 23 121 31.32 24 132 33.13 25 144 34.95 26 156 36.79 27 169 38.65 28 182 40.52 29 196 42.41 30 210 44.31 31 225 46.23

I''ve added the ''n log n'' values as well. Plot these using your graphing tool of choice to see a correlation between the two results. Does this analysis extend to all values of n? I don''t know.


I thought I''d add one comment before posting the 22nd naive solution to the problem. For the naive solution, we don''t need to show that the number of 1''s in the string is at most O(log(n)), but rather that it is at most O(sqrt(n*log(n)).

Solver:

def solve(Str): indexes=[] #O(n) setup for i in range(len(Str)): if Str[i]==''1'': indexes.append(i) #O((number of 1''s)^2) processing for i in range(len(indexes)): for j in range(i+1, len(indexes)): indexDiff = indexes[j] - indexes[i] k=indexes[j] + indexDiff if k<len(Str) and Str[k]==''1'': return True return False

It''s basically a fair bit similar to flybywire''s idea and implementation, though looking ahead instead of back.

Greedy String Builder:

#assumes final char hasn''t been added, and would be a 1 def lastCharMakesSolvable(Str): endIndex=len(Str) j=endIndex-1 while j-(endIndex-j) >= 0: k=j-(endIndex-j) if k >= 0 and Str[k]==''1'' and Str[j]==''1'': return True j=j-1 return False def expandString(StartString=''''): if lastCharMakesSolvable(StartString): return StartString + ''0'' return StartString + ''1'' n=1 BaseStr="" lastCount=0 while n<1000000: BaseStr=expandString(BaseStr) count=BaseStr.count(''1'') if count != lastCount: print(len(BaseStr), count) lastCount=count n=n+1

(In my defense, I''m still in the ''learn python'' stage of understanding)

Also, potentially useful output from the greedy building of strings, there''s a rather consistent jump after hitting a power of 2 in the number of 1''s... which I was not willing to wait around to witness hitting 2096.

strlength # of 1''s 1 1 2 2 4 3 5 4 10 5 14 8 28 9 41 16 82 17 122 32 244 33 365 64 730 65 1094 128 2188 129 3281 256 6562 257 9842 512 19684 513 29525 1024


I thought of a divide-and-conquer approach that might work.

First, in preprocessing you need to insert all numbers less than one half your input size ( n /3) into a list.

Given a string: 0000010101000100 (note that this particular example is valid)

Insert all primes (and 1) from 1 to (16/2) into a list: {1, 2, 3, 4, 5, 6, 7}

Then divide it in half:

100000101 01000100

Keep doing this until you get to strings of size 1. For all size-one strings with a 1 in them, add the index of the string to the list of possibilities; otherwise, return -1 for failure.

You''ll also need to return a list of still-possible spacing distances, associated with each starting index. (Start with the list you made above and remove numbers as you go) Here, an empty list means you''re only dealing with one 1 and so any spacing is possible at this point; otherwise the list includes spacings that must be ruled out.

So continuing with the example above:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

In the first combine step, we have eight sets of two now. In the first, we have the possibility of a set, but we learn that spacing by 1 is impossible because of the other zero being there. So we return 0 (for the index) and {2,3,4,5,7} for the fact that spacing by 1 is impossible. In the second, we have nothing and so return -1. In the third we have a match with no spacings eliminated in index 5, so return 5, {1,2,3,4,5,7}. In the fourth pair we return 7, {1,2,3,4,5,7}. In the fifth, return 9, {1,2,3,4,5,7}. In the sixth, return -1. In the seventh, return 13, {1,2,3,4,5,7}. In the eighth, return -1.

Combining again into four sets of four, we have:

1000 : Return (0, {4,5,6,7}) 0101 : Return (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 0100 : Return (9, {3,4,5,6,7}) 0100 : Return (13, {3,4,5,6,7})

Combining into sets of eight:

10000101 : Return (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 01000100 : Return (9, {4,7}), (13, {3,4,5,6,7})

Combining into a set of sixteen:

10000101 01000100

As we''ve progressed, we keep checking all the possibilities so far. Up to this step we''ve left stuff that went beyond the end of the string, but now we can check all the possibilities.

Basically, we check the first 1 with spacings of 5 and 7, and find that they don''t line up to 1''s. (Note that each check is CONSTANT, not linear time) Then we check the second one (index 5) with spacings of 2, 3, 4, 5, 6, and 7-- or we would, but we can stop at 2 since that actually matches up.

Phew! That''s a rather long algorithm.

I don''t know 100% if it''s O(n log n) because of the last step, but everything up to there is definitely O(n log n) as far as I can tell. I''ll get back to this later and try to refine the last step.

EDIT: Changed my answer to reflect Welbog''s comment. Sorry for the error. I''ll write some pseudocode later, too, when I get a little more time to decipher what I wrote again. ;-)


I''ll give my rough guess here, and let those who are better with calculating complexity to help me on how my algorithm fares in O-notation wise

  1. given binary string 0000010101000100 (as example)
  2. crop head and tail of zeroes -> 00000 101010001 00
  3. we get 101010001 from previous calculation
  4. check if the middle bit is ''one'', if true, found valid three evenly spaced ''ones'' (only if the number of bits is odd numbered)
  5. correlatively, if the remained cropped number of bits is even numbered, the head and tail ''one'' cannot be part of evenly spaced ''one'',
  6. we use 1010100001 as example (with an extra ''zero'' to become even numbered crop), in this case we need to crop again, then becomes -> 10101 00001
  7. we get 10101 from previous calculation, and check middle bit, and we found the evenly spaced bit again

I have no idea how to calculate complexity for this, can anyone help?

edit: add some code to illustrate my idea

edit2: tried to compile my code and found some major mistakes, fixed

char *binaryStr = "0000010101000100"; int main() { int head, tail, pos; head = 0; tail = strlen(binaryStr)-1; if( (pos = find3even(head, tail)) >=0 ) printf("found it at position %d/n", pos); return 0; } int find3even(int head, int tail) { int pos = 0; if(head >= tail) return -1; while(binaryStr[head] == ''0'') if(head<tail) head++; while(binaryStr[tail] == ''0'') if(head<tail) tail--; if(head >= tail) return -1; if( (tail-head)%2 == 0 && //true if odd numbered (binaryStr[head + (tail-head)/2] == ''1'') ) { return head; }else { if( (pos = find3even(head, tail-1)) >=0 ) return pos; if( (pos = find3even(head+1, tail)) >=0 ) return pos; } return -1; }


I''ll try to present a mathematical approach. This is more a beginning than an end, so any help, comment, or even contradiction - will be deeply appreciated. However, if this approach is proven - the algorithm is a straight-forward search in the string.

  1. Given a fixed number of spaces k and a string S , the search for a k-spaced-triplet takes O(n) - We simply test for every 0<=i<=(n-2k) if S[i]==S[i+k]==S[i+2k] . The test takes O(1) and we do it nk times where k is a constant, so it takes O(nk)=O(n) .

  2. Let us assume that there is an Inverse Proportion between the number of 1 ''s and the maximum spaces we need to search for. That is, If there are many 1 ''s, there must be a triplet and it must be quite dense; If there are only few 1 ''s, The triplet (if any) can be quite sparse. In other words, I can prove that if I have enough 1 ''s, such triplet must exist - and the more 1 ''s I have, a more dense triplet must be found. This can be explained by the Pigeonhole principle - Hope to elaborate on this later.

  3. Say have an upper bound k on the possible number of spaces I have to look for. Now, for each 1 located in S[i] we need to check for 1 in S[i-1] and S[i+1] , S[i-2] and S[i+2] , ... S[ik] and S[i+k] . This takes O((k^2-k)/2)=O(k^2) for each 1 in S - due to Gauss'' Series Summation Formula . Note that this differs from section 1 - I''m having k as an upper bound for the number of spaces, not as a constant space.

We need to prove O(n*log(n)) . That is, we need to show that k*(number of 1''s) is proportional to log(n) .

If we can do that, the algorithm is trivial - for each 1 in S whose index is i , simply look for 1 ''s from each side up to distance k . If two were found in the same distance, return i and k . Again, the tricky part would be finding k and proving the correctness.

I would really appreciate your comments here - I have been trying to find the relation between k and the number of 1 ''s on my whiteboard, so far without success.


No theoretical answer here, but I wrote a quick Java program to explore the running-time behavior as a function of k and n, where n is the total bit length and k is the number of 1''s. I''m with a few of the answerers who are saying that the "regular" algorithm that checks all the pairs of bit positions and looks for the 3rd bit, even though it would require O(k^2) in the worst case, in reality because the worst-case needs sparse bitstrings, is O(n ln n).

Anyway here''s the program, below. It''s a Monte-Carlo style program which runs a large number of trials NTRIALS for constant n, and randomly generates bitsets for a range of k-values using Bernoulli processes with ones-density constrained between limits that can be specified, and records the running time of finding or failing to find a triplet of evenly spaced ones, time measured in steps NOT in CPU time. I ran it for n=64, 256, 1024, 4096, 16384* (still running), first a test run with 500000 trials to see which k-values take the longest running time, then another test with 5000000 trials with narrowed ones-density focus to see what those values look like. The longest running times do happen with very sparse density (eg for n=4096 the running time peaks are in the k=16-64 range, with a gentle peak for mean runtime at 4212 steps @ k=31, max runtime peaked at 5101 steps @ k=58). It looks like it would take extremely large values of N for the worst-case O(k^2) step to become larger than the O(n) step where you scan the bitstring to find the 1''s position indices.

package com.example.math; import java.io.PrintStream; import java.util.BitSet; import java.util.Random; public class EvenlySpacedOnesTest { static public class StatisticalSummary { private int n=0; private double min=Double.POSITIVE_INFINITY; private double max=Double.NEGATIVE_INFINITY; private double mean=0; private double S=0; public StatisticalSummary() {} public void add(double x) { min = Math.min(min, x); max = Math.max(max, x); ++n; double newMean = mean + (x-mean)/n; S += (x-newMean)*(x-mean); // this algorithm for mean,std dev based on Knuth TAOCP vol 2 mean = newMean; } public double getMax() { return (n>0)?max:Double.NaN; } public double getMin() { return (n>0)?min:Double.NaN; } public int getCount() { return n; } public double getMean() { return (n>0)?mean:Double.NaN; } public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } // some may quibble and use n-1 for sample std dev vs population std dev public static void printOut(PrintStream ps, StatisticalSummary[] statistics) { for (int i = 0; i < statistics.length; ++i) { StatisticalSummary summary = statistics[i]; ps.printf("%d/t%d/t%.0f/t%.0f/t%.5f/t%.5f/n", i, summary.getCount(), summary.getMin(), summary.getMax(), summary.getMean(), summary.getStdDev()); } } } public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process { public void setProbability(double d); public boolean getNextBoolean(); } static public class Bernoulli implements RandomBernoulliProcess { final private Random r = new Random(); private double p = 0.5; public boolean getNextBoolean() { return r.nextDouble() < p; } public void setProbability(double d) { p = d; } } static public class TestResult { final public int k; final public int nsteps; public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } } //////////// final private int n; final private int ntrials; final private double pmin; final private double pmax; final private Random random = new Random(); final private Bernoulli bernoulli = new Bernoulli(); final private BitSet bits; public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) { this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax; this.bits = new BitSet(n); } /* * generate random bit string */ private int generateBits() { int k = 0; // # of 1''s for (int i = 0; i < n; ++i) { boolean b = bernoulli.getNextBoolean(); this.bits.set(i, b); if (b) ++k; } return k; } private int findEvenlySpacedOnes(int k, int[] pos) { int[] bitPosition = new int[k]; for (int i = 0, j = 0; i < n; ++i) { if (this.bits.get(i)) { bitPosition[j++] = i; } } int nsteps = n; // first, it takes N operations to find the bit positions. boolean found = false; if (k >= 3) // don''t bother doing anything if there are less than 3 ones. :( { int lastBitSetPosition = bitPosition[k-1]; for (int j1 = 0; !found && j1 < k; ++j1) { pos[0] = bitPosition[j1]; for (int j2 = j1+1; !found && j2 < k; ++j2) { pos[1] = bitPosition[j2]; ++nsteps; pos[2] = 2*pos[1]-pos[0]; // calculate 3rd bit index that might be set; // the other two indices point to bits that are set if (pos[2] > lastBitSetPosition) break; // loop inner loop until we go out of bounds found = this.bits.get(pos[2]); // we''re done if we find a third 1! } } } if (!found) pos[0]=-1; return nsteps; } /* * run an algorithm that finds evenly spaced ones and returns # of steps. */ public TestResult run() { bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble()); // probability of bernoulli process is randomly distributed between pmin and pmax // generate bit string. int k = generateBits(); int[] pos = new int[3]; int nsteps = findEvenlySpacedOnes(k, pos); return new TestResult(k, nsteps); } public static void main(String[] args) { int n; int ntrials; double pmin = 0, pmax = 1; try { n = Integer.parseInt(args[0]); ntrials = Integer.parseInt(args[1]); if (args.length >= 3) pmin = Double.parseDouble(args[2]); if (args.length >= 4) pmax = Double.parseDouble(args[3]); } catch (Exception e) { System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]"); System.exit(0); return; // make the compiler happy } final StatisticalSummary[] statistics; statistics=new StatisticalSummary[n+1]; for (int i = 0; i <= n; ++i) { statistics[i] = new StatisticalSummary(); } EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax); int printInterval=100000; int nextPrint = printInterval; for (int i = 0; i < ntrials; ++i) { TestResult result = test.run(); statistics[result.k].add(result.nsteps); if (i == nextPrint) { System.err.println(i); nextPrint += printInterval; } } StatisticalSummary.printOut(System.out, statistics); } }


Obviously we need to at least check bunches of triplets at the same time, so we need to compress the checks somehow. I have a candidate algorithm, but analyzing the time complexity is beyond my ability*time threshold.

Build a tree where each node has three children and each node contains the total number of 1''s at its leaves. Build a linked list over the 1''s, as well. Assign each node an allowed cost proportional to the range it covers. As long as the time we spend at each node is within budget, we''ll have an O(n lg n) algorithm.

-

Start at the root. If the square of the total number of 1''s below it is less than its allowed cost, apply the naive algorithm. Otherwise recurse on its children.

Now we have either returned within budget, or we know that there are no valid triplets entirely contained within one of the children. Therefore we must check the inter-node triplets.

Now things get incredibly messy. We essentially want to recurse on the potential sets of children while limiting the range. As soon as the range is constrained enough that the naive algorithm will run under budget, you do it. Enjoy implementing this, because I guarantee it will be tedious. There''s like a dozen cases.

-

The reason I think that algorithm will work is because the sequences without valid triplets appear to go alternate between bunches of 1''s and lots of 0''s. It effectively splits the nearby search space, and the tree emulates that splitting.

The run time of the algorithm is not obvious, at all. It relies on the non-trivial properties of the sequence. If the 1''s are really sparse then the naive algorithm will work under budget. If the 1''s are dense, then a match should be found right away. But if the density is ''just right'' (eg. near ~n^0.63, which you can achieve by setting all bits at positions with no ''2'' digit in base 3), I don''t know if it will work. You would have to prove that the splitting effect is strong enough.


Ok, I''m going to take another stab at the problem. I think I can prove a O(n log(n)) algorithm that is similar to those already discussed by using a balanced binary tree to store distances between 1''s. This approach was inspired by Justice''s observation about reducing the problem to a list of distances between the 1''s.

Could we scan the input string to construct a balanced binary tree around the position of 1''s such that each node stores the position of the 1 and each edge is labeled with the distance to the adjacent 1 for each child node. Por ejemplo:

10010001 gives the following tree 3 / / 2 / / 3 / / 0 7

This can be done in O(n log(n)) since, for a string of size n, each insertion takes O(log(n)) in the worst case.

Then the problem is to search the tree to discover whether, at any node, there is a path from that node through the left-child that has the same distance as a path through the right child. This can be done recursively on each subtree. When merging two subtrees in the search, we must compare the distances from paths in the left subtree with distances from paths in the right. Since the number of paths in a subtree will be proportional to log(n), and the number of nodes is n, I believe this can be done in O(n log(n)) time.

Did I miss anything?


One inroad into the problem is to think of factors and shifting.

With shifting, you compare the string of ones and zeroes with a shifted version of itself. You then take matching ones. Take this example shifted by two:

1010101010 1010101010 ------------ 001010101000

The resulting 1''s (bitwise ANDed), must represent all those 1''s which are evenly spaced by two. The same example shifted by three:

1010101010 1010101010 ------------- 0000000000000

In this case there are no 1''s which are evenly spaced three apart.

So what does this tell you? Well that you only need to test shifts which are prime numbers. For example say you have two 1''s which are six apart. You would only have to test ''two'' shifts and ''three'' shifts (since these divide six). Por ejemplo:

10000010 10000010 (Shift by two) 10000010 10000010 (We have a match) 10000010 10000010 (Shift by three) 10000010 (We have a match)

So the only shifts you ever need to check are 2,3,5,7,11,13 etc. Up to the prime closest to the square root of size of the string of digits.

Nearly solved?

I think I am closer to a solution. Basically:

  1. Scan the string for 1''s. For each 1 note it''s remainder after taking a modulus of its position. The modulus ranges from 1 to half the size of the string. This is because the largest possible separation size is half the string. This is done in O(n^2). BUT. Only prime moduli need be checked so O(n^2/log(n))
  2. Sort the list of modulus/remainders in order largest modulus first, this can be done in O(n*log(n)) time.
  3. Look for three consecutive moduli/remainders which are the same.
  4. Somehow retrieve the position of the ones!

I think the biggest clue to the answer, is that the fastest sort algorithms, are O(n*log(n)).

WRONG

Step 1 is wrong as pointed out by a colleague. If we have 1''s at position 2,12 and 102. Then taking a modulus of 10, they would all have the same remainders, and yet are not equally spaced apart! Sorry.


This may help....

This problem reduces to the following:

Given a sequence of positive integers, find a contiguous subsequence partitioned into a prefix and a suffix such that the sum of the prefix of the subsequence is equal to the sum of the suffix of the subsequence.

For example, given a sequence of [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ] , we would find a subsequence of [ 3, 6, 5, 2, 2] with a prefix of [ 3, 6 ] with prefix sum of 9 and a suffix of [ 5, 2, 2 ] with suffix sum of 9 .

The reduction is as follows:

Given a sequence of zeros and ones, and starting at the leftmost one, continue moving to the right. Each time another one is encountered, record the number of moves since the previous one was encountered and append that number to the resulting sequence.

For example, given a sequence of [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ] , we would find the reduction of [ 1, 3, 4] . From this reduction, we calculate the contiguous subsequence of [ 1, 3, 4] , the prefix of [ 1, 3] with sum of 4 , and the suffix of [ 4 ] with sum of 4 .

This reduction may be computed in O(n) .

Unfortunately, I am not sure where to go from here.


This seemed liked a fun problem so I decided to try my hand at it.

I am making the assumption that 111000001 would find the first 3 ones and be successful. Essentially the number of zeroes following the 1 is the important thing, since 0111000 is the same as 111000 according to your definition. Once you find two cases of 1, the next 1 found completes the trilogy.

Here it is in Python:

def find_three(bstring): print bstring dict = {} lastone = -1 zerocount = 0 for i in range(len(bstring)): if bstring[i] == ''1'': print i, '': 1'' if lastone != -1: if(zerocount in dict): dict[zerocount].append(lastone) if len(dict[zerocount]) == 2: dict[zerocount].append(i) return True, dict else: dict[zerocount] = [lastone] lastone = i zerocount = 0 else: zerocount = zerocount + 1 #this is really just book keeping, as we have failed at this point if lastone != -1: if(zerocount in dict): dict[zerocount].append(lastone) else: dict[zerocount] = [lastone] return False, dict

This is a first try, so I''m sure this could be written in a cleaner manner. Please list the cases where this method fails down below.


Wasn''t able to come up with the solution yet :(, but have some ideas.

What if we start from a reverse problem: construct a sequence with the maximum number of 1s and WITHOUT any evenly spaced trios. If you can prove the maximum number of 1s is o(n), then you can improve your estimate by iterating only through list of 1s only.


While scanning 1s, add their positions to a List. When adding the second and successive 1s, compare them to each position in the list so far. Spacing equals currentOne (center) - previousOne (left). The right-side bit is currentOne + spacing. If it''s 1, the end.

The list of ones grows inversely with the space between them. Simply stated, if you''ve got a lot of 0s between the 1s (as in a worst case), your list of known 1s will grow quite slowly.

using System; using System.Collections.Generic; namespace spacedOnes { class Program { static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1}; static void Main(string[] args) { var bytes = new byte[4]; var r = new Random(); r.NextBytes(bytes); foreach (var b in bytes) { Console.Write(getByteString(b)); } Console.WriteLine(); var bitCount = bytes.Length * 8; var done = false; var onePositions = new List<int>(); for (var i = 0; i < bitCount; i++) { if (isOne(bytes, i)) { if (onePositions.Count > 0) { foreach (var knownOne in onePositions) { var spacing = i - knownOne; var k = i + spacing; if (k < bitCount && isOne(bytes, k)) { Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing)); done = true; break; } } } if (done) { break; } onePositions.Add(i); } } Console.ReadKey(); } static String getByteString(byte b) { var s = new char[8]; for (var i=0; i<s.Length; i++) { s[i] = ((b & _bits[i]) > 0 ? ''1'' : ''0''); } return new String(s); } static bool isOne(byte[] bytes, int i) { var byteIndex = i / 8; var bitIndex = i % 8; return (bytes[byteIndex] & _bits[bitIndex]) > 0; } } }


# <algorithm> def contains_evenly_spaced?(input) return false if input.size < 3 one_indices = [] input.each_with_index do |digit, index| next if digit == 0 one_indices << index end return false if one_indices.size < 3 previous_indexes = [] one_indices.each do |index| if !previous_indexes.empty? previous_indexes.each do |previous_index| multiple = index - previous_index success_index = index + multiple return true if input[success_index] == 1 end end previous_indexes << index end return false end # </algorithm> def parse_input(input) input.chars.map { |c| c.to_i } end

I''m having trouble with the worst-case scenarios with millions of digits. Fuzzing from /dev/urandom essentially gives you O(n), but I know the worst case is worse than that. I just can''t tell how much worse. For small n , it''s trivial to find inputs at around 3*n*log(n) , but it''s surprisingly hard to differentiate those from some other order of growth for this particular problem.

Can anyone who was working on worst-case inputs generate a string with length greater than say, one hundred thousand?