Мне нужно создать функцию, которая принимает строку, и она должна возвращать true
или в false
зависимости от того, состоит ли ввод из повторяющейся последовательности символов. Длина данной строки всегда больше, 1
а последовательность символов должна иметь как минимум одно повторение.
"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)
Я создал следующую функцию:
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
Проверка этого - часть реальной проблемы. Я не могу позволить себе такое неэффективное решение. Прежде всего, это петля через половину струны.
Вторая проблема заключается в том, что он используется replace()
в каждом цикле, что замедляет его работу. Есть ли лучшее решение относительно производительности?
javascript
string
algorithm
Махир Али
источник
источник
Ответы:
Есть отличная маленькая теорема о таких строках.
Здесь поворот означает удаление некоторого количества символов из передней части строки и перемещение их назад. Например, строку
hello
можно повернуть, чтобы сформировать любую из этих строк:Чтобы понять, почему это работает, сначала предположим, что строка состоит из k повторяющихся копий строки w. Затем удаление первой копии повторяющегося рисунка (w) с передней части струны и прикрепление ее к задней части вернет ту же самую строку. Обратное направление немного сложнее доказать, но идея состоит в том, что если вы повернете строку и вернетесь к тому, с чего начали, вы можете многократно применить это вращение, чтобы выложить строку несколькими копиями одного и того же шаблона (этот шаблон является строка, которую вам нужно было переместить в конец, чтобы выполнить вращение).
Теперь вопрос в том, как проверить, так ли это. Для этого мы можем использовать еще одну красивую теорему:
В качестве примера мы видим, что
lohel
это вращениеhello
следующим образом:В нашем случае мы знаем, что каждая строка x всегда будет подстрокой xx (она будет появляться дважды, по одному разу в каждой копии x). Итак, в основном нам просто нужно проверить, является ли наша строка x подстрокой xx, не позволяя ей совпадать с первым или половинным символом. Вот краткое описание этого:
Предполагая,
indexOf
что реализовано с использованием алгоритма быстрого сопоставления строк, это будет выполняться за время O (n), где n - длина входной строки.Надеюсь это поможет!
источник
Вы можете сделать это с помощью группы захвата и обратной ссылки . Просто проверьте, что это повтор первого захваченного значения.
В приведенном выше RegExp:
^
и$
обозначает начало и конец привязки для прогнозирования положения.(.+)
фиксирует любой шаблон и фиксирует значение (кроме\n
).\1
- обратная ссылка на первое захваченное значение и\1+
будет проверять повторение захваченного значения.Объяснение регулярного выражения здесь
Для отладки RegExp используйте: https://regex101.com/r/pqlAuP/1/debugger
Производительность: https://jsperf.com/reegx-and-loop/13
источник
If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).
но, как вы писали, вы используете обратную ссылку, так что это все еще O (n)?[\s\S]
вместо,.
если вам нужно сопоставить символы новой строки так же, как и другие символы. Символ точки не соответствует символам новой строки; альтернатива ищет все пробельные и непробельные символы, что означает, что в совпадение включаются символы новой строки. (Обратите внимание, что это быстрее, чем более интуитивно понятный(.|[\r\n])
.) Однако, если строка определенно не содержит новых строк, тогда простой.
будет самым быстрым. Обратите внимание, что это будет намного проще, если будет реализован флаг dotall ./^(.+?)\1+$/
немного быстрее? (12 шагов против 20 шагов)Возможно, самый быстрый алгоритмический подход - это построение Z-функции за линейное время:
Реализация на C ++ для справки:
Реализация JavaScript
Добавлены оптимизации - построение половины z-массива и ранний выход
Затем вам нужно проверить индексы,
i
которые делят n. Если вы найдете такоеi
,i+z[i]=n
то строкуs
можно сжать до нужной длиныi
и можно будет вернутьсяtrue
.Например, для
z-массив
и мы можем найти это для
поэтому
s
может быть представлен как подстрока длины 4, повторяемая три раза.источник
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; }
Прочитал ответ gnasher729 и реализовал. Идея в том, что если есть какие-то повторы, то должно быть (также) простое количество повторов.
Немного другой алгоритм таков:
Я обновил страницу jsPerf, которая содержит алгоритмы, используемые на этой странице.
источник
function*
меня, как я, это объявление генератора, а не обычной функции. См. MDNПредположим, что строка S имеет длину N и состоит из дубликатов подстроки s, тогда длина s делит N. Например, если S имеет длину 15, то подстрока имеет длину 1, 3 или 5.
Пусть S состоит из (p * q) копий s. Тогда S также состоит из p копий (s, повторяется q раз). Таким образом, у нас есть два случая: если N - простое число или 1, то S можно сделать только из копий подстроки длины 1. Если N составное, то нам нужно только проверить подстроки s длины N / p на наличие простых p, делящих длина S.
Итак, определите N = длину S, а затем найдите все его простые множители за время O (sqrt (N)). Если есть только один множитель N, проверьте, является ли S одной и той же строкой, повторенной N раз, в противном случае для каждого простого множителя p проверьте, состоит ли S из p повторений первых N / p символов.
источник
Я думаю, что рекурсивная функция тоже может быть очень быстрой. Первое наблюдение заключается в том, что максимальная длина повторяющегося шаблона составляет половину длины всей строки. И мы могли бы просто протестировать все возможные длины повторяющихся шаблонов: 1, 2, 3, ..., str.length / 2
Рекурсивная функция isRepeating (p, str) проверяет, повторяется ли этот шаблон в str.
Если str длиннее шаблона, рекурсия требует, чтобы первая часть (той же длины, что и p) была повторением, а также оставшаяся часть str. Таким образом, str эффективно разбивается на части длиной p.length.
Если тестируемый шаблон и str имеют одинаковый размер, рекурсия здесь успешно завершается.
Если длина отличается (это происходит для «aba» и шаблона «ab») или если части разные, то возвращается false, распространяя рекурсию вверх.
Производительность: https://jsperf.com/reegx-and-loop/13
источник
if( str===p.repeat(str.length/i) ) return true;
вместо использования рекурсивной функции?Написал это на Python. Я знаю, что это не платформа, но это заняло 30 минут. PS => PYTHON
источник
Мой подход похож на gnasher729 в том, что он использует потенциальную длину подстроки в качестве основного фокуса, но он менее математичен и требует больших усилий:
L: длина исходной строки
S: возможные длины допустимых подстрок
Выполните цикл S от (целой части) L / 2 до 1. Если L / S является целым числом, проверьте исходную строку на соответствие первым S-символам исходной строки, повторенным L / S раз.
Причина зацикливания с L / 2 назад, а не с 1 вперед, состоит в том, чтобы получить максимально возможную подстроку. Если вам нужен наименьший возможный цикл подстроки от 1 до L / 2. Пример: «abababab» имеет как «ab», так и «abab» в качестве возможных подстрок. Какой из двух будет быстрее, если вас интересует только истинный / ложный результат, зависит от типа строк / подстрок, к которым они будут применяться.
источник
Следующий код системы Mathematica почти определяет, повторяется ли список хотя бы один раз. Если строка повторяется хотя бы один раз, она возвращает true, но может также возвращать true, если строка является линейной комбинацией повторяющихся строк.
Этот код ищет "полноразмерный" вклад, который должен быть равен нулю в повторяющейся строке, но строка
accbbd
также считается повторяющейся, так как это сумма двух повторяющихся строкababab
и012012
.Идея состоит в том, чтобы использовать быстрое преобразование Фурье и искать частотные спектры. Глядя на другие частоты, можно также обнаружить этот странный сценарий.
источник
Основная идея здесь - исследовать любую потенциальную подстроку, начиная с длины 1 и заканчивая половиной длины исходной строки. Мы смотрим только на длину подстроки, которая равномерно делит исходную длину строки (т.е. str.length% substring.length == 0).
Эта реализация просматривает первый символ каждой возможной итерации подстроки перед переходом ко второму символу, что может сэкономить время, если предполагается, что подстроки будут длинными. Если после проверки всей подстроки несоответствия не обнаружено, мы возвращаем true.
Мы возвращаем false, когда у нас заканчиваются потенциальные подстроки для проверки.
источник
Я не знаком с JavaScript, поэтому не знаю, насколько быстро это будет, но вот решение с линейным временем (при условии разумной встроенной реализации) с использованием только встроенных функций. Опишу алгоритм в псевдокоде.
Идея аналогична ответу MBo. Для каждого,
i
что делит длину,str
это повторение его первыхi
символов тогда и только тогда, когда оно остается таким же после сдвига дляi
символов.Мне приходит в голову, что такая встроенная функция может быть недоступна или неэффективна. В этом случае всегда можно реализовать алгоритм KMP вручную, что требует примерно того же количества кода, что и алгоритм в ответе MBo.
источник
i
,s[0:n-i] == s[i:n]
или , что эквивалентно,s == s[i:n] + s[0:i]
. Зачем второй строке нужно выяснять, были ли в ней повторы?str
себя к формеt
, затем просматриваете,t
чтобы попытаться найтиstr
внутриt
. Хорошо, это может сработать (я отозвал свой голос против). Однако это не линейно в strlen (str). Saystr
имеет длину L. Затем в каждой позиции p = 0,1,2, ..., проверяя, принимает ли str [0..L-1] == t [p..p + L-1] значение O (L ) время. Вам нужно выполнять проверки O (L), когда вы просматриваете значения p, поэтому это O (L ^ 2).Одна из простых идей - заменить строку подстрокой "", и если какой-либо текст существует, то он ложный, иначе это правда.
источник