Самая длинная подстрока, повторяющаяся по крайней мере k раз - PullRequest
0 голосов
/ 15 апреля 2020

Мне дана строка большой длины (скажем, 100 000) и целое число k, и я должен вычислить длину самой большой подстроки, которая повторяется в данной строке как минимум k раз.
Я нашел ответы на этот конкретный вопрос здесь и здесь , но я хотел бы знать, есть ли другой эффективный метод для решения этого вопроса, кроме суффиксных деревьев?

1 Ответ

1 голос
/ 17 апреля 2020

В комментариях было большое обсуждение, я думаю, что лучше написать ответ, чтобы подвести итог. TL; DR Самая длинная подстрока, повторяющаяся по крайней мере k раз

Существует менее эффективный метод, но его действительно легче понять, чем суффиксные деревья: все, что вам нужно знать, - это полиномиальное хеширование и двоичный поиск.

1. Строка полинома га sh

Подробнее об этом можно прочитать здесь https://cp-algorithms.com/string/string-hashing.html. Ниже приведено краткое описание этой техники.

Допустим, у нас есть строка s, целые числа p и mod. Теперь мы можем определить функцию ha sh:

hash(s) = (ord(s[0])*p^(len(s)-1) + ord(s[1])*p^(len(s)-2) + ... + ord(s[len(s)-1])*p^0) % mod 

, где ord - это функция, возвращающая целое число за символ (скажем, это ASCII-код символа). Полиномиальный га sh может быть легко вычислен для каждого префикса строки в O (len (s)) :

# h[i] is a hash of prefix of length i.
# For example s = "abacaba",
# h[0] = hash("") = 0
# h[1] = hash("a")
# h[2] = hash("ab")
# ...
# h[7] = hash("abacaba")

h[0] = 0
for i in 1..n:
    h[i] = (h[i-1] * p + ord(s[i-1])) % mod

Также давайте предварительно вычислим p ^ 0% мод, p ^ 1% mod, ..., p ^ len (s)% mod в массиве pow:

# pow[i] is (p^i) % mod
pow[0] = 1
for i in 1..n:
    pow[i] = (pow[i-1] * p) % mod

Используя массивы h и pow, мы можем легко вычислить га sh любой подстроки строки s:

# get_substring_hash returns hash(s[l] + s[l+1] + ... + s[r-1]).
def get_substring_hash(s, l, r):
    value = h[r] - h[l]*pow[r-l]    # line a
    return (value%mod + mod) % mod  # line b

Давайте разберемся, почему работает приведенный выше код.

h[r] = (ord(s[r-1])*p^0 + ord(s[r-2])*p^1 + ... + ord(s[l-1])*p^(r-l) + ord(s[l-2])*p^(r-l+1) + ...) % mod
h[l] = (                                          ord(s[l-1])*p^0     + ord(s[l-2])*p^1       + ...) % mod
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Как видите, нам нужна только ^^^ -part от h[r], поэтому мы должны избавиться от ~~~ -части. ~~~ -часть в h[r] p^(r-l) раз больше, чем в h[l], и этим объясняется линия a .

линия b является своего рода волхвами c при работе с % mod, value после строка a может быть отрицательной, поэтому value%mod + mod определенно делает положительным. В то же время, если value был положительным после строки , то value%mod + mod больше, чем mod, поэтому (value%mod + mod) % mod обязательно вернет значение в диапазоне 0, 1, ..., mod-1 .

Наконец, mod - это большое простое число, такое как 10 ^ 9 + 7 и value - небольшое число, но больше, чем любой возможный ASCII -код вроде 239 (читайте в статье, почему так).

Некоторые примечания:

  • Хэши могут конфликтовать, потому что у нас есть только mod возможных значений ха sh но количество строк бесконечно. Как с этим бороться читайте в статье.
  • Для выполнения таких операций, как h[r] - h[l]*pow[r-l], может потребоваться использование 64-битных типов целых чисел.

2. Двоичный поиск

Просто прочитайте об этом в Википедии, там нет ничего конкретного c https://en.wikipedia.org/wiki/Binary_search_algorithm.

3. Самая длинная подстрока, повторяющая не менее k раз решение

Допустим, мы предварительно вычислили массивы h и pow. Давайте сделаем бинарный поиск, чтобы найти максимальную длину ans строки, чтобы в данной строке было k или более таких подстрок s.

Почему здесь работает бинарный поиск? Потому что если есть некоторая длина x, такая как * k или более равных подстрок в s длины x, то определенно k или более равных подстрок в s длины x-1 (просто удалите последнюю букву из наших совпадений).

Как будет работать бинарный поиск? Допустим, мы сейчас проверяем, есть ли k или более равных подстрок длины mid. Мы собираемся вычислить все хэши длины mid (используя get_substring_hash), и мы примем решение об изменении границ бинарного поиска, если есть k равных хэшей или нет.

Например: s = "abcabcdefgdefgdefgdefg", k = 3 . Ответ: "defgdefg" :

abcabcdefgdefgdefgdefg
      ^^^^^^^^          occurence 1
          ^^^^^^^^      occurence 2
              ^^^^^^^^  occurence 3

Итерации бинарного поиска:

l =  1, r = 22, mid = 11. No substring of length 11 satisfy.
l =  1, r = 10, mid =  5. There should be hash("defgd")    be seen 3 times.
l =  5, r = 10, mid =  7. There should be hash("defgdef")  be seen 3 times.
l =  7, r = 10, mid =  8. There should be hash("defgdefg") be seen 3 times.
l =  8, r = 10, mid =  9. No substring of length 9  satisfy.
l =  8, r =  8.           That means answer is 8.

Как видите, сложность составляет O (n log n) : round (log n) бинарные итерации поиска и O (n) сложность на итерацию, если вы используете что-то вроде std::unordered_map, чтобы проверить, есть ли ха sh с > = k случаев или нет.

Я действительно надеюсь, что теперь все ясно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...