Je dois créer une fonction qui prend une chaîne, et elle doit retourner true
ou en false
fonction du fait que l'entrée consiste en une séquence de caractères répétée. La longueur de la chaîne donnée est toujours supérieure à 1
et la séquence de caractères doit avoir au moins une répétition.
"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)
J'ai créé la fonction ci-dessous:
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
Vérifier cela fait partie du vrai problème. Je ne peux pas me permettre une solution non efficace comme celle-ci. Tout d'abord, il parcourt la moitié de la corde.
Le deuxième problème est qu'il utilise replace()
dans chaque boucle, ce qui le ralentit. Existe-t-il une meilleure solution concernant les performances?
javascript
string
algorithm
Maheer Ali
la source
la source
Réponses:
Il existe un petit théorème astucieux sur les cordes comme celles-ci.
Ici, une rotation signifie supprimer un certain nombre de caractères de l'avant de la chaîne et les déplacer vers l'arrière. Par exemple, la chaîne
hello
peut être tournée pour former l'une de ces chaînes:Pour voir pourquoi cela fonctionne, supposons d'abord qu'une chaîne se compose de k copies répétées d'une chaîne w. Ensuite, en supprimant la première copie du motif répété (w) à l'avant de la corde et en la plaçant au dos, la même corde sera renvoyée. La direction inverse est un peu plus délicate à prouver, mais l'idée est que si vous faites pivoter une chaîne et récupérez ce avec quoi vous avez commencé, vous pouvez appliquer cette rotation à plusieurs reprises pour mettre en mosaïque la chaîne avec plusieurs copies du même motif (ce motif étant le chaîne dont vous aviez besoin pour aller à la fin pour faire la rotation).
Maintenant, la question est de savoir comment vérifier si tel est le cas. Pour cela, il existe un autre beau théorème que nous pouvons utiliser:
À titre d'exemple, nous pouvons voir qu'il
lohel
s'agit d'une rotation de lahello
manière suivante:Dans notre cas, nous savons que chaque chaîne x sera toujours une sous-chaîne de xx (elle apparaîtra deux fois, une fois à chaque copie de x). Donc, fondamentalement, nous avons juste besoin de vérifier si notre chaîne x est une sous-chaîne de xx sans lui permettre de correspondre au premier ou à mi-chemin. Voici un one-liner pour cela:
En supposant qu'il
indexOf
soit implémenté à l'aide d'un algorithme de correspondance de chaîne rapide, cela s'exécutera au temps O (n), où n est la longueur de la chaîne d'entrée.J'espère que cela t'aides!
la source
Vous pouvez le faire par un groupe de capture et une référence arrière . Vérifiez simplement qu'il s'agit de la répétition de la première valeur capturée.
Dans le RegExp ci-dessus:
^
et$
représente les ancres de début et de fin pour prédire la position.(.+)
capture n'importe quel motif et capture la valeur (sauf\n
).\1
est la référence arrière de la première valeur capturée et\1+
vérifierait la répétition de la valeur capturée.Explication Regex ici
Pour le débogage RegExp, utilisez: https://regex101.com/r/pqlAuP/1/debugger
Performances: https://jsperf.com/reegx-and-loop/13
la source
If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).
mais comme vous l'avez écrit, vous utilisez la référence arrière, est-ce toujours O (n)?[\s\S]
place de.
si vous avez besoin de faire correspondre les caractères de nouvelle ligne de la même manière que les autres caractères. Le caractère point ne correspond pas aux nouvelles lignes; l'alternative recherche tous les espaces blancs et non blancs, ce qui signifie que les retours à la ligne sont inclus dans la correspondance. (Notez que c'est plus rapide que le plus intuitif(.|[\r\n])
.) Cependant, si la chaîne ne contient certainement pas de nouvelles lignes, alors le simple.
sera le plus rapide. Notez que ce sera beaucoup plus simple si l'indicateur dotall est implémenté./^(.+?)\1+$/
un peu plus rapide? (12 étapes vs 20 étapes)L'approche algorithmique la plus rapide consiste peut-être à construire une fonction Z en temps linéaire:
Implémentation C ++ pour référence:
Implémentation JavaScript
Optimisations ajoutées - création de la moitié du z-array et sortie anticipée
Ensuite, vous devez vérifier les index
i
qui divisent n. Si vous trouvez teli
quei+z[i]=n
la chaînes
peut être compressée à la longueuri
et vous pouvez retournertrue
.Par exemple, pour
z-array est
et nous pouvons trouver que pour
ainsi
s
pourrait être représenté comme sous-chaîne de longueur 4 répétée trois fois.la source
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; }
J'ai lu la réponse de gnasher729 et l'ai implémentée. L'idée est que s'il y a des répétitions, alors il doit y avoir (aussi) un nombre premier de répétitions.
Un algorithme légèrement différent est le suivant:
J'ai mis à jour la page jsPerf qui contient les algorithmes utilisés sur cette page.
la source
function*
pour la première fois comme moi, c'est pour déclarer un générateur, pas une fonction ordinaire. Voir MDNSupposons que la chaîne S a la longueur N et est composée de doublons de la sous-chaîne s, alors la longueur de s divise N. Par exemple, si S a la longueur 15, alors la sous-chaîne a la longueur 1, 3 ou 5.
Soit S constitué de (p * q) copies de s. Alors S est également constitué de p copies de (s, répétées q fois). On a donc deux cas: si N est premier ou 1, alors S ne peut être fait que de copies de la sous-chaîne de longueur 1. Si N est composite, alors il suffit de vérifier les sous-chaînes s de longueur N / p pour les nombres premiers p divisant la longueur de S.
Déterminez donc N = la longueur de S, puis trouvez tous ses facteurs premiers dans le temps O (sqrt (N)). S'il n'y a qu'un seul facteur N, vérifiez si S est la même chaîne répétée N fois, sinon pour chaque facteur premier p, vérifiez si S est constitué de p répétitions des N / p premiers caractères.
la source
Je pense qu'une fonction récursive pourrait également être très rapide. La première observation est que la longueur maximale du motif répété est la moitié de la longueur totale de la chaîne. Et nous pourrions simplement tester toutes les longueurs de motifs répétés possibles: 1, 2, 3, ..., str.length / 2
La fonction récursive isRepeating (p, str) teste si ce modèle est répété dans str.
Si str est plus long que le motif, la récursivité nécessite que la première partie (même longueur que p) soit une répétition ainsi que le reste de str. Ainsi, str est effectivement divisé en morceaux de longueur p. Longueur.
Si le motif et la chaîne testés sont de taille égale, la récursivité se termine ici, avec succès.
Si la longueur est différente (se produit pour "aba" et le motif "ab") ou si les morceaux sont différents, alors false est renvoyé, propageant la récursivité.
Performances: https://jsperf.com/reegx-and-loop/13
la source
if( str===p.repeat(str.length/i) ) return true;
au lieu d'utiliser une fonction récursive?A écrit ceci en Python. Je sais que ce n'est pas la plate-forme, mais cela a pris 30 minutes de temps. PS => PYTHON
la source
Mon approche est similaire à gnasher729, en ce sens qu'elle utilise la longueur potentielle de la sous-chaîne comme objectif principal, mais elle est moins gourmande en mathématiques et en processus:
L: longueur de la chaîne d'origine
S: longueurs potentielles des sous-chaînes valides
Boucle S de (partie entière de) L / 2 à 1. Si L / S est un entier, vérifiez votre chaîne d'origine par rapport aux premiers caractères S de la chaîne d'origine répétée L / S fois.
La raison de la boucle à partir de L / 2 vers l'arrière et non à partir de 1 est d'obtenir la plus grande sous-chaîne possible. Si vous voulez la plus petite boucle de sous-chaîne possible de 1 à L / 2. Exemple: "abababab" a à la fois "ab" et "abab" comme sous-chaînes possibles. Lequel des deux serait plus rapide si vous ne vous souciez que d'un résultat vrai / faux dépend du type de chaînes / sous-chaînes auxquelles il sera appliqué.
la source
Le code Mathematica suivant détecte presque si la liste est répétée au moins une fois. Si la chaîne est répétée au moins une fois, elle renvoie true, mais elle peut également renvoyer true si la chaîne est une combinaison linéaire de chaînes répétées.
Ce code recherche la contribution «pleine longueur», qui doit être égale à zéro dans une chaîne répétitive, mais la chaîne
accbbd
est également considérée comme répétée, car il s'agit d'une somme des deux chaînes répétéesababab
et012012
.L'idée est d'utiliser la transformée de Fourier rapide et de rechercher les spectres de fréquence. En regardant d'autres fréquences, on devrait également pouvoir détecter cet étrange scénario.
la source
L'idée de base ici est d'examiner toute sous-chaîne potentielle, commençant à la longueur 1 et s'arrêtant à la moitié de la longueur de la chaîne d'origine. Nous examinons uniquement les longueurs de sous-chaîne qui divisent la longueur de la chaîne d'origine de manière égale (c'est-à-dire str.length% substring.length == 0).
Cette implémentation examine le premier caractère de chaque itération de sous-chaîne possible avant de passer au second caractère, ce qui peut gagner du temps si les sous-chaînes sont censées être longues. Si aucune incompatibilité n'est trouvée après avoir examiné la sous-chaîne entière, nous retournons true.
Nous retournons false lorsque nous manquons de sous-chaînes potentielles à vérifier.
la source
Je ne suis pas familier avec JavaScript, donc je ne sais pas à quelle vitesse cela va être, mais voici une solution de temps linéaire (en supposant une implémentation intégrée raisonnable) utilisant uniquement des fonctions intégrées. Je vais décrire l'algorithme en pseudocode.
L'idée est similaire à la réponse de MBo. Pour chacun
i
qui divise la longueur,str
est une répétition de ses premiersi
caractères si et seulement si elle reste la même après le décalage desi
caractères.Il me vient à l'esprit qu'un tel intégré peut être indisponible ou inefficace. Dans ce cas, il est toujours possible d'implémenter manuellement l' algorithme KMP , ce qui prend à peu près la même quantité de code que l'algorithme de la réponse de MBo.
la source
i
,s[0:n-i] == s[i:n]
ou équivalent,s == s[i:n] + s[0:i]
. Pourquoi la deuxième ligne doit-elle déterminer si elle a eu des répétitions?str
à lui-même pour formert
, puis scannezt
pour essayer de trouver à l'str
intérieurt
. D'accord, cela peut fonctionner (j'ai retiré mon vote défavorable). Ce n'est pas linéaire dans strlen (str), cependant. Disons qu'ilstr
est de longueur L. Puis à chaque position p = 0,1,2, ..., en vérifiant si str [0..L-1] == t [p..p + L-1] prend O (L ) temps. Vous devez faire des vérifications O (L) au fur et à mesure que vous parcourez les valeurs de p, donc c'est O (L ^ 2).Une des idées simples est de remplacer la chaîne par la sous-chaîne de "" et si un texte existe, alors il est faux, sinon c'est vrai.
la source