Поиск кратчайшего повторяющегося цикла в слове?

Я собираюсь написать функцию, которая вернет мне самый короткий период группы букв, который в конечном итоге создаст данное слово.

Например, слово abkebabkebabkeb создается повторяющимся словом abkeb. Я хотел бы знать, как эффективно анализировать входное слово, чтобы получить кратчайший период символов, создающих входное слово.

Ответы

Ответ 1

O (n). Предполагается, что вся строка должна быть закрыта. Главное наблюдение заключается в том, что мы генерируем шаблон и проверяем его, но если мы найдем что-то по тому, что не соответствует, мы должны включить всю строку, которую мы уже тестировали, поэтому нам не нужно повторно брать эти символы.

def pattern(inputv):
    pattern_end =0
    for j in range(pattern_end+1,len(inputv)):

        pattern_dex = j%(pattern_end+1)
        if(inputv[pattern_dex] != inputv[j]):

            pattern_end = j;
            continue

        if(j == len(inputv)-1):
            print pattern_end
            return inputv[0:pattern_end+1];
    return inputv;

Ответ 2

Вот правильный алгоритм O (n). Первый цикл for - это этап построения таблицы KMP. Существуют различные доказательства того, что он всегда работает в линейном времени.

Поскольку у этого вопроса есть 4 предыдущих ответа, ни один из которых не является O (n) и правильным, я сильно тестировал это решение как для корректности, так и для времени выполнения.

def pattern(inputv):
    if not inputv:
        return inputv

    nxt = [0]*len(inputv)
    for i in range(1, len(nxt)):
        k = nxt[i - 1]
        while True:
            if inputv[i] == inputv[k]:
                nxt[i] = k + 1
                break
            elif k == 0:
                nxt[i] = 0
                break
            else:
                k = nxt[k - 1]

    smallPieceLen = len(inputv) - nxt[-1]
    if len(inputv) % smallPieceLen != 0:
        return inputv

    return inputv[0:smallPieceLen]

Ответ 3

Это пример для PHP:

<?php
function getrepeatedstring($string) {
    if (strlen($string)<2) return $string;
    for($i = 1; $i<strlen($string); $i++) {
        if (substr(str_repeat(substr($string, 0, $i),strlen($string)/$i+1), 0, strlen($string))==$string)
            return substr($string, 0, $i);
    }
    return $string;
}
?>

Ответ 4

Я считаю, что есть очень изящное рекурсивное решение. Многие из предложенных решений решают дополнительную сложность, когда строка заканчивается частью шаблона, например abcabca. Но я не думаю, что этого требуют.

Мое решение для простой версии проблемы в clojure:

 (defn find-shortest-repeating [pattern string]
  (if (empty? (str/replace string pattern ""))
   pattern
   (find-shortest-repeating (str pattern (nth string (count pattern))) string)))

(find-shortest-repeating "" "abcabcabc") ;; "abc"

Но имейте в виду, что это не приведет к тому, что шаблоны не будут завершены в конце.

Ответ 5

Я нашел решение на основе вашего сообщения, которое может иметь неполный шаблон:

(defn find-shortest-repeating [pattern string]
   (if (or (empty? (clojure.string/split string (re-pattern pattern)))
          (empty? (second (clojure.string/split string (re-pattern pattern)))))
    pattern
    (find-shortest-repeating (str pattern (nth string (count pattern))) string)))

Ответ 6

Мое решение. Идея состоит в том, чтобы найти подстроку из нулевой позиции, чтобы она становилась равной смежной подстроке той же длины, когда такая подстрока найдена, возвращая подстроку. Обратите внимание, если не найдено никакой повторяющейся подстроки. Я печатаю всю строку ввода.

public static void repeatingSubstring(String input){
    for(int i=0;i<input.length();i++){
        if(i==input.length()-1){
            System.out.println("There is no repetition "+input);
        }
        else if(input.length()%(i+1)==0){
            int size = i+1;
            if(input.substring(0, i+1).equals(input.substring(i+1, i+1+size))){
                System.out.println("The subString which repeats itself is "+input.substring(0, i+1));
                break;
            }
        }
    }
}

Ответ 7

Regex решение:

Шаг 1. Разделите каждый символ разделителем, который не является частью входной строки, включая конечную (т.е. ~):

(.)
$1~

Пример ввода: "abkebabkebabkeb"
Пример вывода: "a~b~k~e~b~a~b~k~e~b~a~b~k~e~b~"

Попробуйте онлайн в Retina. (ПРИМЕЧАНИЕ. Retina - это язык программирования на основе Regex, предназначенный для быстрого тестирования регулярных выражений и способности успешно конкурировать в задачах с кодированием кода).

Шаг 2. Используйте следующее регулярное выражение, чтобы найти кратчайшую повторяющуюся подстроку (где ~ - наш выбранный символ разделителя):

^(([^~]+~)*?)\1*$
$1

Объяснение:

^(([^~]+~)*?)\1*$
^               $    # Start and end, to match the entire input-string
  ([^~]+~)           # Capture group 1: One or more non-'~' followed by a '~'
 (        *?)        # Capture group 2: Repeated zero or more time optionally
             \1*     # Followed by the first capture group repeated zero or more times

$1                   # Replace the entire input-string with the first capture group match

Пример ввода: "a~b~k~e~b~a~b~k~e~b~a~b~k~e~b~"
Пример вывода: "a~b~k~e~b~"

Попробуйте онлайн в Retina.

Шаг 3: Удалите символ разделителя, чтобы получить наш предполагаемый результат.

~
<empty>

Пример ввода: "a~b~k~e~b~"
Пример вывода: "abkeb"

Попробуйте онлайн в Retina.

Вот пример реализации в Java.

Ответ 8

Ответ с большой задержкой, но я получил вопрос в интервью, вот мой ответ (возможно, не самый оптимальный, но он работает и для странных тестовых случаев).

private void run(String[] args) throws IOException {
    File file = new File(args[0]);
    BufferedReader buffer = new BufferedReader(new FileReader(file));
    String line;
    while ((line = buffer.readLine()) != null) {
        ArrayList<String> subs = new ArrayList<>();
        String t = line.trim();
        String out = null;
        for (int i = 0; i < t.length(); i++) {
            if (t.substring(0, t.length() - (i + 1)).equals(t.substring(i + 1, t.length()))) {
                subs.add(t.substring(0, t.length() - (i + 1)));
            }
        }
        subs.add(0, t);
        for (int j = subs.size() - 2; j >= 0; j--) {
            String match = subs.get(j);
            int mLength = match.length();
            if (j != 0 && mLength <= t.length() / 2) {
                if (t.substring(mLength, mLength * 2).equals(match)) {
                    out = match;
                    break;
                }
            } else {
                out = match;
            }
        }
        System.out.println(out);
    }
}

Testcases:

abcabcabcabc
bcbcbcbcbcbcbcbcbcbcbcbcbcbc
dddddddddddddddddddd
adcdefg
bcbdbcbcbdbc
hellohell

Возврат кода:

а
Ьс
d
adcdefg
bcbdbc
hellohell

Ответ 9

Работает в таких случаях, как bcbdbcbcbdbc.

function smallestRepeatingString(sequence){
  var currentRepeat = '';
  var currentRepeatPos = 0;

  for(var i=0, ii=sequence.length; i<ii; i++){
    if(currentRepeat[currentRepeatPos] !== sequence[i]){
      currentRepeatPos = 0;
      // Add next character available to the repeat and reset i so we don't miss any matches inbetween
      currentRepeat = currentRepeat + sequence.slice(currentRepeat.length, currentRepeat.length+1);
      i = currentRepeat.length-1;
    }else{
      currentRepeatPos++;
    }
    if(currentRepeatPos === currentRepeat.length){
      currentRepeatPos = 0;
    }
  }

  // If repeat wasn't reset then we didn't find a full repeat at the end.
  if(currentRepeatPos !== 0){ return sequence; }

  return currentRepeat;
}

Ответ 10

Я придумал простое решение, которое работает безупречно даже с очень большими строками.
Реализация PHP:

function get_srs($s){
    $hash = md5( $s );
    $i = 0; $p = '';

    do {
        $p .= $s[$i++];
        preg_match_all( "/{$p}/", $s, $m );
    } while ( ! hash_equals( $hash, md5( implode( '', $m[0] ) ) ) );

    return $p;
}