Tengo que crear una función que tome una cadena, y debería regresar true
o false
basarse en si la entrada consiste en una secuencia de caracteres repetida. La longitud de la cadena dada siempre es mayor 1
y la secuencia de caracteres debe tener al menos una repetición.
"aa" // true(entirely contains two strings "a")
"aaa" //true(entirely contains three string "a")
"abcabcabc" //true(entirely containas three strings "abc")
"aba" //false(At least there should be two same substrings and nothing more)
"ababa" //false("ab" exists twice but "a" is extra so false)
He creado la siguiente función:
function check(str){
if(!(str.length && str.length - 1)) return false;
let temp = '';
for(let i = 0;i<=str.length/2;i++){
temp += str[i]
//console.log(str.replace(new RegExp(temp,"g"),''))
if(!str.replace(new RegExp(temp,"g"),'')) return true;
}
return false;
}
console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false
Verificar esto es parte del verdadero problema. No puedo permitirme una solución no eficiente como esta. En primer lugar, está recorriendo la mitad de la cadena.
El segundo problema es que está usando replace()
en cada ciclo lo que lo hace lento. ¿Existe una mejor solución con respecto al rendimiento?
javascript
string
algorithm
Maheer Ali
fuente
fuente
Respuestas:
Hay un pequeño teorema ingenioso sobre cadenas como estas.
Aquí, una rotación significa eliminar algunos caracteres del frente de la cadena y moverlos hacia atrás. Por ejemplo, la cadena
hello
podría girarse para formar cualquiera de estas cadenas:Para ver por qué esto funciona, primero, suponga que una cadena consiste en k copias repetidas de una cadena w. Luego, eliminar la primera copia del patrón repetido (w) del frente de la cadena y pegarlo en la parte posterior devolverá la misma cadena. La dirección inversa es un poco más difícil de probar, pero la idea es que si gira una cadena y recupera lo que comenzó, puede aplicar esa rotación repetidamente para colocar la cadena en mosaico con varias copias del mismo patrón (ese patrón es el cadena que necesitaba para moverse hasta el final para hacer la rotación).
Ahora la pregunta es cómo verificar si este es el caso. Para eso, hay otro hermoso teorema que podemos usar:
Como ejemplo, podemos ver que
lohel
es una rotación de lahello
siguiente manera:En nuestro caso, sabemos que cada cadena x siempre será una subcadena de xx (aparecerá dos veces, una en cada copia de x). Básicamente, solo tenemos que verificar si nuestra cadena x es una subcadena de xx sin permitir que coincida en el primer carácter o en el medio. Aquí hay una frase para eso:
Suponiendo que
indexOf
se implemente utilizando un algoritmo de coincidencia de cadenas rápido, esto se ejecutará en el tiempo O (n), donde n es la longitud de la cadena de entrada.¡Espero que esto ayude!
fuente
Puede hacerlo mediante un grupo de captura y referencia inversa . Solo verifique que sea la repetición del primer valor capturado.
En el RegExp anterior:
^
y$
representa los anclajes de inicio y fin para predecir la posición.(.+)
captura cualquier patrón y captura el valor (excepto\n
).\1
es la referencia del primer valor capturado y\1+
verificaría la repetición del valor capturado.Explicación de expresiones regulares aquí
Para el uso de depuración RegExp: https://regex101.com/r/pqlAuP/1/debugger
Rendimiento: https://jsperf.com/reegx-and-loop/13
fuente
If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).
pero como escribiste, estás usando referencia inversa, ¿sigue siendo O (n)?[\s\S]
lugar de.
si necesita unir caracteres de nueva línea de la misma manera que otros caracteres. El carácter de punto no coincide en las nuevas líneas; la alternativa busca todos los caracteres de espacios en blanco y no espacios en blanco, lo que significa que se incluyen nuevas líneas en la coincidencia. (Tenga en cuenta que esto es más rápido que el más intuitivo(.|[\r\n])
). Sin embargo, si la cadena definitivamente no contiene nuevas líneas, entonces lo simple.
será más rápido. Tenga en cuenta que esto será mucho más simple si se implementa la bandera dotall ./^(.+?)\1+$/
un poco más rápido? (12 pasos vs 20 pasos)Quizás el enfoque algorítmico más rápido es construir una función Z en tiempo lineal:
Implementación de C ++ para referencia:
Implementación de JavaScript
Optimizaciones agregadas: creación de la mitad de la matriz z y salida anticipada
Luego debe verificar los índices
i
que dividen n. Si encuentra talesi
quei+z[i]=n
entonces la cadenas
se puede comprimir a la longitudi
y puede volvertrue
.Por ejemplo, para
z-array es
y podemos encontrar eso para
entonces
s
podría representarse como una subcadena de longitud 4 repetida tres veces.fuente
return z.some((zi, i) => (i + zi) === n && n % i === 0)
const check = (s) => { let n = s.length; let z = Array(n).fill(0); for (let i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r) z[i] = Math.min(r - i + 1, z[i - l]); while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; // check condition here and return if (z[i] + i === n && n % i === 0) return true; if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1; } // or return false return false; }
Leí la respuesta de gnasher729 y la implementé. La idea es que si hay repeticiones, debe haber (también) un número primo de repeticiones.
Un algoritmo ligeramente diferente es este:
He actualizado la página jsPerf que contiene los algoritmos utilizados en esta página.
fuente
function*
por primera vez como yo, es para declarar un generador, no una función regular. Ver MDNSuponga que la cadena S tiene longitud N y está hecha de duplicados de la subcadena s, luego la longitud de s divide N. Por ejemplo, si S tiene longitud 15, entonces la subcadena tiene longitud 1, 3 o 5.
Sea S hecho de (p * q) copias de s. Entonces S también está hecho de p copias de (s, repetidas q veces). Por lo tanto, tenemos dos casos: si N es primo o 1, entonces S solo puede estar hecho de copias de la subcadena de longitud 1. Si N es compuesto, entonces solo necesitamos verificar las subcadenas s de longitud N / p para los primos p dividiendo la longitud de S.
Entonces determine N = la longitud de S, luego encuentre todos sus factores primos en el tiempo O (sqrt (N)). Si solo hay un factor N, verifique si S es la misma cadena repetida N veces; de lo contrario, para cada factor primo p, verifique si S consiste en repeticiones p de los primeros N / p caracteres.
fuente
Creo que una función recursiva también podría ser muy rápida. La primera observación es que la longitud máxima del patrón repetido es la mitad del largo de la cadena total. Y podríamos probar todas las longitudes posibles de patrones repetidos: 1, 2, 3, ..., longitud de str / 2
La función recursiva isRepeating (p, str) prueba si este patrón se repite en str.
Si str es más largo que el patrón, la recursión requiere que la primera parte (la misma longitud que p) sea una repetición, así como el resto de str. Entonces str se divide efectivamente en pedazos de longitud p.length.
Si el patrón probado y str son del mismo tamaño, la recursión termina aquí, con éxito.
Si la longitud es diferente (sucede para "aba" y el patrón "ab") o si las piezas son diferentes, entonces se devuelve falso, propagando la recursividad.
Rendimiento: https://jsperf.com/reegx-and-loop/13
fuente
if( str===p.repeat(str.length/i) ) return true;
lugar de usar una función recursiva?Escribió esto en Python. Sé que no es la plataforma, pero tomó 30 minutos de tiempo. PS => PITÓN
fuente
Mi enfoque es similar al de gnasher729, ya que utiliza la longitud potencial de la subcadena como el foco principal, pero es menos matemático y requiere un proceso intensivo:
L: longitud de la cuerda original
S: longitudes potenciales de subcadenas válidas
Bucle S de (parte entera de) L / 2 a 1. Si L / S es un entero, verifique su cadena original con los primeros caracteres S de la cadena original repetidos L / S veces.
La razón para realizar un bucle desde L / 2 hacia atrás y no desde 1 en adelante es para obtener la subcadena más grande posible. Si desea el bucle de subcadena más pequeño posible de 1 a L / 2. Ejemplo: "abababab" tiene "ab" y "abab" como posibles subcadenas. ¿Cuál de los dos sería más rápido si solo le importa un resultado verdadero / falso depende del tipo de cadenas / subcadenas a las que se aplicará?
fuente
El siguiente código de Mathematica casi detecta si la lista se repite al menos una vez. Si la cadena se repite al menos una vez, devuelve verdadero, pero también puede devolver verdadero si la cadena es una combinación lineal de cadenas repetidas.
Este código busca la contribución de "longitud completa", que debe ser cero en una cadena repetitiva, pero la cadena
accbbd
también se considera repetida, ya que es una suma de las dos cadenas repetidasababab
y012012
.La idea es usar la Transformada rápida de Fourier y buscar los espectros de frecuencia. Al observar otras frecuencias, uno también debería ser capaz de detectar este extraño escenario.
fuente
La idea básica aquí es examinar cualquier subcadena potencial, comenzando en la longitud 1 y deteniéndose en la mitad de la longitud de la cadena original. Solo observamos las longitudes de las subcadenas que dividen la longitud de la cadena original de manera uniforme (es decir, longitud de cadena% substring.length == 0).
Esta implementación analiza el primer carácter de cada posible iteración de subcadena antes de pasar al segundo carácter, lo que podría ahorrar tiempo si se espera que las subcadenas sean largas. Si no se encuentra ninguna discrepancia después de examinar toda la subcadena, entonces devolvemos verdadero.
Devuelve falso cuando nos quedamos sin subcadenas potenciales para verificar.
fuente
No estoy familiarizado con JavaScript, por lo que no sé qué tan rápido será, pero aquí hay una solución de tiempo lineal (suponiendo una implementación integrada razonable) utilizando solo las incorporadas. Describiré el algoritmo en pseudocódigo.
La idea es similar a la respuesta de MBo. Para cada uno
i
que divide la longitud,str
es una repetición de sus primerosi
caracteres si y solo si permanece igual después de cambiar dei
caracteres.Me viene a la mente que tal construcción puede no estar disponible o ser ineficiente. En este caso, siempre es posible implementar el algoritmo KMP manualmente, que requiere aproximadamente la misma cantidad de código que el algoritmo en la respuesta de MBo.
fuente
i
,s[0:n-i] == s[i:n]
o lo que es equivalente,s == s[i:n] + s[0:i]
. ¿Por qué la segunda línea necesita determinar si tuvo alguna repetición?str
a sí mismo para formart
, luego escaneat
para tratar de encontrar elstr
interiort
. De acuerdo, esto puede funcionar (he retractado mi voto negativo). Sin embargo, no es lineal en strlen (str). Saystr
es de longitud L. Luego, en cada posición p = 0,1,2, ..., verificando si str [0..L-1] == t [p..p + L-1] toma O (L ) hora. Debe realizar verificaciones de O (L) a medida que avanza por los valores de p, por lo que es O (L ^ 2).Una de las ideas simples es reemplazar la cadena con la subcadena de "" y si existe algún texto, entonces es falso, de lo contrario es cierto.
fuente