Ayer tuve esta pregunta en una prueba de Algoritmos, y no puedo encontrar la respuesta. Me está volviendo loco, porque valía unos 40 puntos. Supongo 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 existen. Escriba un algoritmo que resuelva esto en tiempo O (n * log (n)).
Así que las 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 "uniformemente espaciada". Entonces 1001011 es un número válido. Con 1, 4 y 7 que son espaciados uniformemente.
Respuestas:
¡Finalmente! Siguiendo los leads en la respuesta de sdcvvc , lo tenemos: ¡el algoritmo O (n log n) para el problema! También es simple, después de que lo entiendes. Los que adivinaron FFT tenían razón.
El problema: se nos da una cadena binaria
S
de longitud n , y queremos encontrar tres 1s espaciados uniformemente en ella. Por ejemplo,S
puede ser110110010
, donde n = 9. Ha espaciado uniformemente 1s en las posiciones 2, 5 y 8.Escanee de
S
izquierda a derecha y haga una listaL
de posiciones de 1. Para loS=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 enL
, es decir, encontrar distintas a, b, c enL
tal que ba = cb , o equivalentemente a + c = 2b . Para el ejemplo anterior, queremos encontrar la progresión (2, 5, 8).Haz un polinomio
p
con términos x k para cada k inL
. Para el ejemplo anterior, hacemos el polinomio p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Este paso es O (n).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).Ignore 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 decide hacerlo.Aquí está el punto crucial: el coeficiente de cualquier x 2b para b en
L
es precisamente el número de pares (a, c) enL
tal que a + c = 2b . [CLRS, ej. 30.1-7] Uno de esos pares es (b, b) siempre (entonces 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 por el AP (2,5,8). (Estos coeficientes x 2bsiempre 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 no es un b en
L
para el que el coeficiente de x 2b es mayor que 1, entonces sabemos que hay algún par (a, c) - que no sea (b, b) - para el que a + c = 2b . Para encontrar el par real, simplemente intentamos cada a inL
(la c correspondiente sería 2b-a ) y vemos si hay un 1 en la posición 2b-a inS
. Este paso es O (n).Eso es todo amigos.
Uno podría preguntarse: ¿necesitamos usar FFT? Muchas respuestas, como beta , flybywire y rsp , sugieren que el enfoque que verifica cada par de 1s y ve si hay un 1 en la "tercera" posición, podría funcionar en O (n log n), según la intuición que si hay demasiados 1s, encontraríamos un triple fácilmente, y si hay muy pocos 1s, verificar 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). Dicha cadena tiene 2 k = n (log 2) / (log 3) ≈ n 0.63 unos en ella y no 1s espaciados uniformemente, por lo que verificar todos los pares sería del orden del cuadrado del número de 1s: 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 construyó (efectivamente) tales cadenas que tienen n 1-c / √ (log n) 1s en ellas pero no 1s espaciadas uniformemente, lo que significa que en tales cadenas, lo simple el enfoque tomaría Θ (n 2-2c / √ (log n) )- Sólo una pequeña poco mejor que Θ (n 2 ) , es sorprendente!
Aproximadamente el número máximo de 1s en una cadena de longitud n sin 3 espaciados uniformemente (lo que vimos arriba era al menos n 0.63 de la construcción fácil de 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 de 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 sólo 8 (11011000011011000000000000), y para n = 74 podemos obtener 22 1s (11000010110001000001011010001000000000000000010001011010000010001101000011) pero el codicioso sólo da 16 (11011000011011000000000000011011000011011000000000000000000000000000000000). Sin embargo, están de acuerdo en bastantes lugares hasta 50 (por ejemplo, todos de 38 a 50). Como dicen las referencias de OEIS, parece que Jaroslaw Wroblewski está interesado en esta pregunta, y mantiene un sitio web sobre estos conjuntos que no promedian . Los números exactos se conocen solo hasta 194.
fuente
Su problema se llama PROMEDIO en este documento (1999):
Wikipedia :
Esto es suficiente para resolver tu 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 según 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 tienen esto en cuenta están intentando resolver un problema abierto.
Otra información interesante, mayormente irreverente:
Límite inferior:
Un límite inferior fácil es un conjunto tipo Cantor (números 1..3 ^ n-1 que no contienen 1 en su expansión ternaria); su densidad es n ^ (log_3 2) (circa 0.631). Entonces, verificar 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 más inteligente. Aquí 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 antiguo algoritmo:
Se sabe que para n grande, un subconjunto de {1,2, ..., n} que no contiene progresión aritmética tiene a lo sumo n / (log n) ^ (1/20) elementos. El artículo Sobre triples en progresión aritmética demuestra más: el conjunto no puede contener más de n * 2 28 * (log log n / log n) 1/2 elementos. Por lo tanto, puede verificar si se logra ese límite y, de lo contrario, verificar ingenuamente los pares. Este es el algoritmo O (n 2 * log log n / log n), más rápido que O (n 2 ). Desafortunadamente "On triples ..." está en Springer, pero la primera página está disponible, y la exposición de Ben Green está disponible aquí , página 28, teorema 24.
Por cierto, los documentos son de 1999, el mismo año que el primero que mencioné, así que probablemente es por eso que el primero no menciona ese resultado.
fuente
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 el número máximo de unos, y todos son bastante interesantes, obtuve hasta 125 dígitos y aquí están los primeros 3 números que encontré al intentar insertar tantos bits '1' como sea posible:
Tenga en cuenta que todos son fractales (no es demasiado sorprendente dadas las restricciones). Puede haber algo en pensar hacia atrás, tal vez si la cadena no es un fractal con una característica, ¿entonces debe tener un patrón repetitivo?
Gracias a beta por el mejor término para describir estos números.
Actualización: Por desgracia, parece que el patrón se rompe al comenzar con una cadena inicial lo suficientemente grande, como: 10000000000001:
fuente
Sospecho que un enfoque simple que se parece a O (n ^ 2) en realidad producirá algo mejor, como O (n ln (n)). Las secuencias que tardan más en probarse (para cualquier n dado) son las que no contienen tríos, y eso impone restricciones severas en el número de 1 que puede estar en la secuencia.
Se me ocurrieron algunos argumentos para agitar las manos, pero no he podido encontrar una prueba ordenada. Voy a apuñalar en la oscuridad: la respuesta es una idea muy inteligente que el profesor ha sabido durante tanto tiempo que parece obvio, pero es demasiado difícil para los estudiantes. (O eso o dormiste en la conferencia que lo cubrió).
fuente
Revisión: 2009-10-17 23:00
He ejecutado 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 lo suficientemente genial y contiene una serie de optimizaciones que hacen que funcione realmente rápido. Evalúa todos los arreglos de cadenas binarias de 24 dígitos o menos en menos de 25 segundos.
He actualizado el código para incluir la
0 <= L < M < U <= X-1
observación de hoy.Original
Esto es, en concepto, similar a otra pregunta que respondí . Ese código también analizó tres valores en una serie y determinó si un triplete cumplía una condición. Aquí hay un código C # adaptado de eso:
Las principales diferencias son:
Este código genera un conjunto poderoso de datos para encontrar la entrada más difícil de resolver para este algoritmo.
El código de la pregunta anterior generó todas las soluciones utilizando un generador de Python. Este código solo muestra lo más difícil para cada longitud de patrón.
Este código verifica la distancia desde el elemento medio hasta su borde izquierdo y derecho. El código de Python probó si una suma estaba por encima o por debajo de 0.
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 medio. Este último cambio ofrece una gran mejora en el rendimiento.
base a las observaciones al final de este artículo, 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 mediante el cálculo previo de la información. En consecuencia, el código usa dos niveles de indirección en el bucle principal de FindCandidate y requiere dos llamadas a FindCandidate para cada elemento intermedio: una para números pares y otra para números impares.
La idea general es trabajar en índices, no en la representación en bruto de los datos. Calcular un conjunto 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 mientras mantiene el problema equivalente.
Los resultados están desactualizados: eliminados.
Editar: 2009-10-16 18:48
En los datos de yx, a los que se les da cierta credibilidad en las otras respuestas como representativos de datos duros para calcular, obtengo estos resultados ... los eliminé. Están desactualizados.
Señalaría que estos datos no son los más difíciles 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 es errónea. El peor de los casos para un algoritmo en particular, espero, dependerá del algoritmo en sí mismo y probablemente no será consistente en diferentes algoritmos.
Editar: 2009-10-17 13:30
Más 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
tal que
o
Como A [L] y A [U] suman un número par, no pueden ser (par, impar) o (impar, par). La búsqueda de una coincidencia podría mejorarse dividiendo A [] en grupos pares e impares y buscando coincidencias en A [M] en los grupos de candidatos pares e impares.
Sin embargo, creo que es más una optimización del rendimiento que una mejora algorítmica. El número de comparaciones debería disminuir, pero el orden del algoritmo debería ser el mismo.
Editar 2009-10-18 00:45
Sin embargo, se me ocurre otra optimización, en la misma línea que separar a los candidatos en pares e impares. Como los tres índices tienen que agregarse a un múltiplo de 3 (a, a + x, a + 2x - mod 3 es 0, independientemente de a y x), puede separar L, M y U en sus valores de mod 3 :
De hecho, podría combinar esto con la observación par / impar y separarlos en sus valores mod 6:
y así. Esto proporcionaría una mayor optimización del rendimiento, pero no una aceleración algorítmica.
fuente
No pude encontrar la solución todavía :(, pero tengo algunas ideas.
¿Qué pasa si partimos de un problema inverso: construimos una secuencia con el número máximo de 1s y SIN tríos espaciados uniformemente? Si puede probar que el número máximo de 1s es o (n), puede mejorar su estimación iterando solo a través de la lista de 1s solamente.
fuente
Esto puede ayudar ...
Este problema se reduce a lo siguiente:
Por ejemplo, dada una secuencia de
[ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ]
, encontraríamos una subsecuencia de[ 3, 6, 5, 2, 2]
con un prefijo de[ 3, 6 ]
suma de prefijo de9
y un sufijo de[ 5, 2, 2 ]
con suma de sufijo de9
.La reducción es la siguiente:
Por ejemplo, dada una secuencia de
[ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ]
, encontraríamos la reducción de[ 1, 3, 4]
. A partir de esta reducción, calculamos la subsecuencia contigua de[ 1, 3, 4]
, el prefijo de[ 1, 3]
con suma de4
y el sufijo de[ 4 ]
con suma de4
.Esta reducción puede calcularse en
O(n)
.Desafortunadamente, no estoy seguro de a dónde ir desde aquí.
fuente
Para el tipo de problema simple (es decir, busca tres "1" con solo (es decir, cero o más) "0" entre ellos), es bastante simple: puede dividir la secuencia en cada "1" y buscar dos subsecuencias adyacentes que tengan la misma longitud (la segunda subsecuencia no es la última, por supuesto). Obviamente, esto se puede hacer en O (n) tiempo.
Para la versión más compleja (es decir, busca un índice i y una brecha g > 0 tal que
s[i]==s[i+g]==s[i+2*g]=="1"
), no estoy seguro, si existe una solución O (n log n) , ya que posiblemente haya trillizos O (n²) que tienen esta propiedad (piense en una cadena de todos, hay aproximadamente n² / 2 de estos tripletes). Por supuesto, solo está buscando uno de estos, pero actualmente no tengo idea de cómo encontrarlo ...fuente
Una pregunta divertida, pero una vez que te das cuenta de que el patrón real entre dos '1' no importa, el algoritmo se convierte en:
En código, JTest fashion (tenga en cuenta que este código no está escrito para ser más eficiente y agregué algunos println para ver qué sucede).
fuente
Pensé en un enfoque de divide y vencerás que podría funcionar.
Primero, en el preprocesamiento debe insertar todos los números de menos de la mitad de su tamaño de entrada ( n / 3) en una lista.
Dada una cadena:
0000010101000100
(tenga en cuenta que este ejemplo en particular es válido)Inserte todos los primos (y 1) del 1 al (16/2) en una lista: {1, 2, 3, 4, 5, 6, 7}
Luego divídalo por la mitad:
100000101 01000100
Siga haciendo esto hasta llegar a cadenas de tamaño 1. Para todas las cadenas de tamaño uno con un 1 en ellas, agregue el índice de la cadena a la lista de posibilidades; de lo contrario, devuelve -1 por falla.
También deberá devolver una lista de distancias de separación aún posibles, asociadas con cada índice inicial. (Comience con la lista que hizo arriba y elimine los números a medida que avanza) Aquí, una lista vacía significa que solo está tratando con un 1 y, por lo tanto, cualquier espacio es posible en este punto; de lo contrario, la lista incluye espacios que deben descartarse.
Continuando con el ejemplo anterior:
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
En el primer paso combinado, tenemos ocho conjuntos de dos ahora. En el primero, tenemos la posibilidad de un conjunto, pero aprendemos que el espacio entre 1 es imposible debido a que el otro cero está allí. Entonces devolvemos 0 (para el índice) y {2,3,4,5,7} por el hecho de que el espaciado entre 1 es imposible. En el segundo, no tenemos nada y, por lo tanto, devuelve -1. En el tercero tenemos una coincidencia sin espacios eliminados en el índice 5, así que devuelve 5, {1,2,3,4,5,7}. En el cuarto par devolvemos 7, {1,2,3,4,5,7}. En el quinto, devuelve 9, {1,2,3,4,5,7}. En el sexto, devuelve -1. En el séptimo, devuelve 13, {1,2,3,4,5,7}. En el octavo, devuelve -1.
Combinando nuevamente en cuatro conjuntos de cuatro, tenemos:
1000
: Retorno (0, {4,5,6,7})0101
: Retorno (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6 , 7})0100
: Retorno (9, {3,4,5,6,7})0100
: Retorno (13, {3,4,5,6,7})Combinando en conjuntos de ocho:
10000101
: Retorno (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7})01000100
: Retorno (9, {4,7}), (13, {3,4,5,6,7})Combinando en un conjunto de dieciséis:
10000101 01000100
A medida que avanzamos, seguimos comprobando todas las posibilidades hasta ahora. Hasta este paso, hemos dejado cosas que iban más allá del final de la cadena, pero ahora podemos verificar todas las posibilidades.
Básicamente, verificamos el primer 1 con espacios de 5 y 7, y encontramos que no se alinean con los 1. (Tenga en cuenta que cada verificación es CONSTANTE, no lineal). Luego verificamos la segunda (índice 5) con espacios de 2, 3, 4, 5, 6 y 7, o lo haríamos, pero podemos detenernos en 2 desde eso realmente coincide.
¡Uf! Ese es un algoritmo bastante largo.
No sé 100% si es O (n log n) debido al último paso, pero todo lo que hay hasta allí definitivamente es O (n log n) por lo que puedo decir. Volveré a esto más tarde e intentaré refinar el último paso.
EDITAR: Cambié mi respuesta para reflejar el comentario de Welbog. Lo siento por el error. Escribiré un pseudocódigo más tarde, también, cuando tenga un poco más de tiempo para descifrar lo que escribí nuevamente. ;-)
fuente
100010001
? Si entiendo su enfoque correctamente, no podrá igualarlo porque(0,{4})
no es posible calcular la respuesta correcta . Dado que necesita no primos en su lista, es fácil encontrar cadenas patológicas que inflen las listas de posibilidades que necesita verificar a más de O (n log (n)), creo.Daré mi conjetura aquí, y dejaré que aquellos que son mejores calculando la complejidad me ayuden a saber cómo funciona mi algoritmo en cuanto a la notación O
No tengo idea de cómo calcular la complejidad para esto, ¿alguien puede ayudarme?
editar: agregue un código para ilustrar mi idea
edit2: intenté compilar mi código y encontré algunos errores importantes, solucionado
fuente
Se me ocurrió algo como esto:
Esto está inspirado en andycjw.
En cuanto a la complejidad, esto podría ser O (nlogn) ya que en cada recursión estamos dividiendo por dos.
Espero eso ayude.
fuente
Ok, voy a dar otra puñalada al problema. Creo que puedo probar un algoritmo O (n log (n)) que es similar a los ya discutidos mediante el uso de un árbol binario equilibrado para almacenar distancias entre 1. Este enfoque se inspiró en la observación de Justice sobre reducir el problema a una lista de distancias entre los 1's.
¿Podríamos escanear la cadena de entrada para construir un árbol binario equilibrado alrededor de la posición de 1 de manera que cada nodo almacene la posición del 1 y cada borde esté etiquetado con la distancia al 1 adyacente para cada nodo secundario. Por ejemplo:
Esto se puede hacer en O (n log (n)) ya que, para una cadena de tamaño n, cada inserción toma O (log (n)) en el peor de los casos.
Entonces, el problema es buscar en el árbol para descubrir si, en cualquier nodo, hay una ruta desde ese nodo a través del elemento secundario izquierdo que tiene la misma distancia que un camino a través del elemento secundario derecho. Esto puede hacerse recursivamente en cada subárbol. Al fusionar dos subárboles en la búsqueda, debemos comparar las distancias de los caminos en el subárbol izquierdo con las distancias de los caminos en el derecho. Dado que el número de rutas en un subárbol será proporcional a log (n), y el número de nodos es n, creo que esto se puede hacer en el tiempo O (n log (n)).
¿Yo me perdí algo?
fuente
Esto parecía un problema divertido, así que decidí probarlo.
Estoy asumiendo que 111000001 encontraría los primeros 3 y tendría éxito. Esencialmente, el número de ceros después del 1 es lo importante, ya que 0111000 es el mismo que 111000 según su definición. Una vez que encuentre dos casos de 1, el siguiente 1 encontrado completa la trilogía.
Aquí está en Python:
Este es un primer intento, así que estoy seguro de que podría escribirse de manera más limpia. Enumere los casos en los que este método falla a continuación.
fuente
Supongo que la razón por la que esto es nlog (n) se debe a lo siguiente:
Entonces, tienes n, log (n) y 1 ... O (nlogn)
Editar: Vaya, mi mal. Mi cerebro tenía que establecer que n / 2 era logn ... lo que obviamente no lo es (duplicar el número de elementos todavía duplica el número de iteraciones en el bucle interno). Esto todavía está en n ^ 2, sin resolver el problema. Bueno, al menos tengo que escribir un código :)
Implementación en Tcl
fuente
Creo que he encontrado una manera de resolver el problema, pero no puedo construir una prueba formal. La solución que hice está escrita en Java, y utiliza un contador 'n' para contar cuántos accesos a la lista / matriz tiene. Entonces n debería ser menor o igual que stringLength * log (stringLength) si es correcto. Lo probé para los números del 0 al 2 ^ 22, y funciona.
Comienza iterando sobre la cadena de entrada y haciendo una lista de todos los índices que contienen uno. Esto es solo O (n).
Luego, de la lista de índices, elige un firstIndex y un secondIndex que es mayor que el primero. Estos dos índices deben contener unos, porque están en la lista de índices. A partir de ahí, se puede calcular el tercer índice. Si inputString [thirdIndex] es un 1, entonces se detiene.
}
nota adicional: el contador n no se incrementa cuando itera sobre la cadena de entrada para construir la lista de índices. Esta operación es O (n), por lo que no tendrá un efecto en la complejidad del algoritmo de todos modos.
fuente
O(n^2)
algoritmo.Una de las vías del problema es pensar en factores y cambios.
Con el desplazamiento, compara la cadena de unos y ceros con una versión desplazada de sí mismo. Luego tomas los que coinciden. Tome este ejemplo desplazado por dos:
Los 1's resultantes (AND a nivel de bit), deben representar todos aquellos 1 que están espaciados uniformemente por dos. El mismo ejemplo cambió por tres:
En este caso, no hay 1 que estén espaciados uniformemente entre sí.
Entonces, ¿qué te dice esto? Bueno, solo necesitas probar turnos que son números primos. Por ejemplo, supongamos que tiene dos 1 que están separados por seis. Solo tendría que probar los turnos 'dos' y los turnos 'tres' (ya que estos dividen seis). Por ejemplo:
Por lo tanto, los únicos turnos que debe verificar son 2,3,5,7,11,13, etc. Hasta el primo más cercano a la raíz cuadrada del tamaño de la cadena de dígitos.
¿Casi resuelto?
Creo que estoy más cerca de una solución. Básicamente:
Creo que la pista más importante para la respuesta es que los algoritmos de clasificación más rápidos son O (n * log (n)).
INCORRECTO
El paso 1 es incorrecto como lo señaló un colega. Si tenemos 1 en las posiciones 2,12 y 102. Luego, tomando un módulo de 10, ¡todos tendrían los mismos restos y, sin embargo, no estarán separados por igual! Lo siento.
fuente
Aquí hay algunos pensamientos que, a pesar de mis mejores esfuerzos, no parecerán envolverse en una reverencia. Aún así, podrían ser un punto de partida útil para el análisis de alguien.
Considere la solución propuesta de la siguiente manera, que es el enfoque que varias personas han sugerido, incluido yo mismo en una versión anterior de esta respuesta.
:)
Ahora considere cadenas de entrada como las siguientes, que no tendrán una solución:
En general, esta es la concatenación de k cadenas de la forma j 0 seguidas de un 1 para j de cero a k-1.
Tenga en cuenta que las longitudes de las subcadenas son 1, 2, 3, etc. Por lo tanto, el tamaño del problema n tiene subcadenas de longitudes 1 a k, de modo que n = k (k + 1) / 2.
Tenga en cuenta que k también rastrea el número de 1 que tenemos que tener en cuenta. Recuerde que cada vez que vemos un 1, debemos considerar todos los 1 vistos hasta ahora. Entonces, cuando vemos el segundo 1, solo consideramos el primero, cuando vemos el tercer 1, reconsideramos los dos primeros, cuando vemos el cuarto 1, necesitamos reconsiderar los primeros tres, y así sucesivamente. Al final del algoritmo, hemos considerado k (k-1) / 2 pares de 1. Llama a eso p.
La relación entre n y p es que n = p + k.
El proceso de pasar por la cadena lleva O (n) tiempo. Cada vez que se encuentra un 1, se realiza un máximo de (k-1) comparaciones. Como n = k (k + 1) / 2, n> k ** 2, entonces sqrt (n)> k. Esto nos da O (n sqrt (n)) u O (n ** 3/2). Sin embargo, tenga en cuenta que puede que no sea un límite realmente apretado, porque el número de comparaciones va de 1 a un máximo de k, no es k todo el tiempo. Pero no estoy seguro de cómo explicar eso en las matemáticas.
Todavía no es O (n log (n)). Además, no puedo probar que esas entradas sean los peores casos, aunque sospecho que lo son. Creo que un empaque más denso de 1 al frente resulta en un empaque aún más escaso al final.
Como alguien aún puede encontrarlo útil, aquí está mi código para esa solución en Perl:
fuente
Mientras escanea 1s, agregue sus posiciones a una Lista. Cuando agregue el segundo y sucesivos 1s, compárelos con cada posición en la lista hasta el momento. El espaciado es igual a currentOne (centro) - previousOne (izquierda). El bit del lado derecho es currentOne + spacing. Si es 1, el final.
La lista de unos crece inversamente con el espacio entre ellos. En pocas palabras, si tienes muchos 0 entre los 1 (como en el peor de los casos), tu lista de 1 conocidos crecerá muy lentamente.
fuente
Pensé en agregar un comentario antes de publicar la solución ingenua número 22 al problema. Para la solución ingenua, no necesitamos mostrar que el número de 1 en la cadena es como máximo O (log (n)), sino que es como máximo O (sqrt (n * log (n)).
Solucionador:
Básicamente es bastante similar a la idea e implementación de flybywire, aunque mirando hacia adelante en lugar de hacia atrás.
Greedy String Builder:
(En mi defensa, todavía estoy en la etapa de comprensión de 'aprender pitón')
Además, como resultado potencialmente útil de la codiciosa construcción de cuerdas, hay un salto bastante consistente después de golpear una potencia de 2 en el número de 1 ... que no estaba dispuesto a esperar para presenciar el golpe de 2096.
fuente
Trataré de presentar un enfoque matemático. Esto es más un comienzo que un fin, por lo que cualquier ayuda, comentario o incluso contradicción será muy apreciada. Sin embargo, si se prueba este enfoque, el algoritmo es una búsqueda directa en la cadena.
Dado un número fijo de espacios
k
y una cadenaS
, la búsqueda de un triplete con espacio k tomaO(n)
: simplemente probamos cada0<=i<=(n-2k)
siS[i]==S[i+k]==S[i+2k]
. La prueba tomaO(1)
y lo hacemosn-k
veces dondek
es una constante, por lo que tomaO(n-k)=O(n)
.Supongamos que hay una proporción inversa entre el número de
1
's' y los espacios máximos que necesitamos buscar. Es decir, si hay muchos1
, debe haber un triplete y debe ser bastante denso; Si solo hay unos pocos1
, el triplete (si lo hay) puede ser bastante escaso. En otras palabras, puedo demostrar que si tengo suficientes1
, este triplete debe existir, y cuanto más1
tengo, se debe encontrar un triplete más denso. Esto puede explicarse por el principio de Pigeonhole : espero profundizar en esto más adelante.Digamos que tengo un límite superior
k
en el número posible de espacios que tengo que buscar. Ahora, para cada uno1
situado enS[i]
lo que necesitamos para comprobar1
enS[i-1]
yS[i+1]
,S[i-2]
yS[i+2]
, ...S[i-k]
yS[i+k]
. Esto tomaO((k^2-k)/2)=O(k^2)
para cada1
enS
- debido a Gauss Serie Suma Fórmula . Tenga en cuenta que esto difiere de la sección 1: estoy teniendok
como límite superior el número de espacios, no como un espacio constante.Necesitamos probarlo
O(n*log(n))
. Es decir, tenemos que demostrar quek*(number of 1's)
es proporcional alog(n)
.Si podemos hacer eso, el algoritmo es trivial: para cada uno
1
enS
cuyo índice estái
, simplemente busque1
's desde cada lado hasta la distanciak
. Si se encontraron dos en la misma distancia, regresei
yk
. Nuevamente, la parte difícil sería encontrark
y probar la corrección.Realmente agradecería sus comentarios aquí. He estado tratando de encontrar la relación
k
y el número de1
's en mi pizarra, hasta ahora sin éxito.fuente
Suposición:
Simplemente incorrecto, hablando de log (n) número de límite superior de unos
EDITAR:
Ahora descubrí que usando números de Cantor (si es correcto), la densidad en el conjunto es (2/3) ^ Log_3 (n) (qué función más extraña) y estoy de acuerdo, la densidad de log (n) / n es demasiado fuerte.
Si este es el límite superior, hay un algoritmo que resuelve este problema en al menos O (n * (3/2) ^ (log (n) / log (3))) complejidad de tiempo y O ((3/2) ^ ( log (n) / log (3))) complejidad del espacio. (verifique la respuesta de Justice para el algoritmo)
Esto sigue siendo mucho mejor que O (n ^ 2)
Esta función ((3/2) ^ (log (n) / log (3))) realmente se parece a n * log (n) a primera vista.
¿Cómo obtuve esta fórmula?
Aplaudir el número de Cantors en la cuerda.
Suponga que la longitud de la cadena es 3 ^ p == n
En cada paso de la generación de la cadena de Cantor, mantiene 2/3 de la cantidad anterior de unidades. Aplicar esto p veces.
Eso significa (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) restantes y después de la simplificación 2 ^ p. Esto significa 2 ^ p unos en 3 ^ p cadena -> (3/2) ^ p unos. Sustituya p = log (n) / log (3) y obtenga
((3/2) ^ (log (n) / log (3)))
fuente
¿Qué tal una solución simple de O (n), con espacio O (n ^ 2)? (Utiliza el supuesto de que todos los operadores bit a bit funcionan en O (1)).
El algoritmo básicamente funciona en cuatro etapas:
Etapa 1: Para cada bit en su número original, averigüe qué tan lejos están los unos, pero considere solo una dirección. (Considere todos los bits en la dirección del bit menos significativo).
Etapa 2: Invierta el orden de los bits en la entrada;
Etapa 3: vuelva a ejecutar el paso 1 en la entrada invertida.
Etapa 4: Compare los resultados de la Etapa 1 y la Etapa 3. Si algún bit está igualmente espaciado arriba Y abajo, debemos tener un éxito.
Tenga en cuenta que ningún paso en el algoritmo anterior lleva más tiempo que O (n). ^ _ ^
Como beneficio adicional, este algoritmo encontrará TODOS los igualmente espaciados de CADA número. Entonces, por ejemplo, si obtiene un resultado de "0x0005", entonces hay espacios igualmente espaciados en AMBAS unidades 1 y 3 de distancia
Realmente no intenté optimizar el código a continuación, pero es un código C # compilable que parece funcionar.
Alguien probablemente comentará que para cualquier número suficientemente grande, las operaciones bit a bit no pueden realizarse en O (1). Estarías en lo correcto. Sin embargo, conjeturaría que toda solución que use sumas, restas, multiplicaciones o divisiones (que no se puede hacer cambiando) también tendría ese problema.
fuente
A continuación hay una solución. Podría haber algunos pequeños errores aquí y allá, pero la idea es sólida.
Editar: no es n * log (n)
CÓDIGO PSEUDO:
Código C #:
Cómo funciona:
fuente
Obviamente, necesitamos al menos verificar grupos de trillizos al mismo tiempo, por lo que debemos comprimir los controles de alguna manera. Tengo un algoritmo candidato, pero analizar la complejidad del tiempo está más allá de mi capacidad * umbral de tiempo.
Construya un árbol donde cada nodo tenga tres hijos y cada nodo contenga el número total de 1 en sus hojas. Cree una lista vinculada sobre los 1, también. Asigne a cada nodo un costo permitido proporcional al rango que cubre. Mientras el tiempo que pasamos en cada nodo esté dentro del presupuesto, tendremos un algoritmo O (n lg n).
-
Comience en la raíz. Si el cuadrado del número total de 1 por debajo es menor que su costo permitido, aplique el algoritmo ingenuo. De lo contrario, recurse en sus hijos.
Ahora hemos regresado dentro del presupuesto, o sabemos que no hay trillizos válidos completamente contenidos dentro de uno de los niños. Por lo tanto, debemos verificar los tripletes entre nodos.
Ahora las cosas se vuelven increíblemente desordenadas. Esencialmente, queremos recurrir a los conjuntos potenciales de niños mientras limitamos el rango. Tan pronto como el rango esté lo suficientemente limitado como para que el algoritmo ingenuo se ejecute por debajo del presupuesto, lo haces. Disfruta implementando esto, porque te garantizo que será tedioso. Hay como una docena de casos.
-
La razón por la que creo que el algoritmo funcionará es porque las secuencias sin tripletes válidos parecen alternarse entre grupos de 1 y muchos 0. Divide efectivamente el espacio de búsqueda cercano, y el árbol emula esa división.
El tiempo de ejecución del algoritmo no es obvio, en absoluto. Se basa en las propiedades no triviales de la secuencia. Si los 1 son realmente escasos, entonces el ingenuo algoritmo funcionará por debajo del presupuesto. Si los 1 son densos, entonces se debe encontrar una coincidencia de inmediato. Pero si la densidad es "correcta" (p. Ej., Cerca de ~ n ^ 0.63, que puede lograr al establecer todos los bits en posiciones sin dígitos '2' en la base 3), no sé si funcionará. Tendría que demostrar que el efecto de división es lo suficientemente fuerte.
fuente
No hay una respuesta teórica aquí, pero escribí un programa Java rápido para explorar el comportamiento del tiempo de ejecución en función de k y n, donde n es la longitud total de bits yk es el número de 1. Estoy con algunos de los que responden que dicen que el algoritmo "regular" que verifica todos los pares de posiciones de bits y busca el tercer bit, aunque requeriría O (k ^ 2) en el peor de los casos, en realidad porque el peor de los casos necesita cadenas de bits dispersas, es O (n ln n).
De todos modos, aquí está el programa, a continuación. Es un programa de estilo Monte-Carlo que ejecuta una gran cantidad de NTRIALS de prueba para n constante, y genera aleatoriamente conjuntos de bits para un rango de valores k utilizando procesos de Bernoulli con una densidad limitada entre límites que se pueden especificar, y registra el tiempo de ejecución de encontrar o no encontrar un triplete de espaciado uniforme, tiempo medido en pasos NO en tiempo de CPU. Lo ejecuté durante n = 64, 256, 1024, 4096, 16384 * (aún en ejecución), primero una prueba con 500000 pruebas para ver qué valores k toman el tiempo de ejecución más largo, luego otra prueba con 5000000 pruebas con valores reducidos. enfoque de densidad para ver cómo se ven esos valores. Los tiempos de ejecución más largos ocurren con una densidad muy escasa (por ejemplo, para n = 4096 los picos de tiempo de ejecución están en el rango k = 16-64, con un pico suave para el tiempo de ejecución medio en 4212 pasos @ k = 31, el tiempo de ejecución máximo alcanzó su punto máximo en 5101 pasos @ k = 58). Parece que tomaría valores extremadamente grandes de N para que el paso O (k ^ 2) en el peor de los casos se hiciera más grande que el paso O (n) donde escanea la cadena de bits para encontrar los índices de posición del 1.
fuente
Tengo problemas con los peores escenarios con millones de dígitos. Fuzzing de
/dev/urandom
esencialmente te da O (n), pero sé que el peor de los casos es peor que eso. Simplemente no puedo decir cuánto peor. Para los pequeñosn
, es trivial encontrar insumos alrededor3*n*log(n)
, pero es sorprendentemente difícil diferenciarlos de algún otro orden de crecimiento para este problema en particular.¿Puede alguien que estaba trabajando en las entradas del peor de los casos generar una cadena con una longitud mayor que, digamos, cien mil?
fuente
Una adaptación del algoritmo Rabin-Karp podría ser posible para usted. Su complejidad es 0 (n), por lo que podría ayudarlo.
Echa un vistazo http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm
fuente
¿Podría ser esto una solución? No estoy seguro de si es O (nlogn), pero en mi opinión es mejor que O (n²) porque la única forma de no encontrar un triple sería una distribución de números primos.
Hay margen de mejora, el segundo encontrado 1 podría ser el próximo primero 1. Además, no hay comprobación de errores.
fuente
Creo que este algoritmo tiene complejidad O (n log n) (C ++, DevStudio 2k5). Ahora, no conozco los detalles de cómo analizar un algoritmo para determinar su complejidad, por lo que he agregado alguna información de recopilación métrica al código. El código cuenta el número de pruebas realizadas en la secuencia de 1 y 0 para cualquier entrada dada (con suerte, no he hecho una bola del algoritmo). Podemos comparar el número real de pruebas con el valor O y ver si hay una correlación.
Este programa genera el número de pruebas para cada longitud de cadena de hasta 32 caracteres. Aquí están los resultados:
También agregué los valores 'n log n'. Grafique estos utilizando su herramienta gráfica de elección para ver una correlación entre los dos resultados. ¿Este análisis se extiende a todos los valores de n? No lo sé.
fuente