Алгоритм Манакера: Поиск самого длинного палиндрома за линейное время
Палиндром — это строка, которая читается одинаково слева направо и справа налево ("А РОЗА УПАЛА НА ЛАПУ АЗОРА"). Задача поиска самой длинной палиндромной подстроки внутри гигантского текста часто встречается в биоинформатике (поиск симметричных последовательностей в молекулах ДНК) и криптоанализе. Простой перебор всех возможных центров и расширение от них дает сложность O(N^2). В 1975 году Гленн Манакер (Glenn Manacher) создал невероятно элегантный алгоритм, решающий эту задачу за строго линейное время O(N).
Алгоритм Манакера опирается на одно мощное свойство палиндромов — их зеркальную симметрию. Если мы уже нашли большой палиндром, и теперь рассматриваем символ внутри его правой половины, мы можем не вычислять радиус палиндрома для этого символа с нуля! Мы можем "подсмотреть" ответ у симметричного символа в левой половине.
Перед началом поиска алгоритм элегантно решает проблему четных и нечетных палиндромов. Слово "КАБАК" имеет нечетную длину и явный центр "Б". А слово "АННА" имеет четную длину, и его центр находится между буквами. Чтобы не писать два разных алгоритма, Манакер вставляет фиктивные символы (например, "#") между всеми буквами и по краям: #А#Н#Н#А#. Теперь абсолютно все палиндромы становятся нечетной длины и имеют физический центр.
Логика линейного прохода состоит в следующем:
- Алгоритм заводит массив
P, гдеP[i]— это радиус самого большого палиндрома с центром в позицииi. - Алгоритм поддерживает две переменные:
C(центр самого правого из найденных палиндромов) иR(его правая граница). - Когда алгоритм переходит к новой позиции
i(которая находится правееC, но еще внутри границыR), он находит ее зеркального двойника (позициюi') относительно центраC. - Математика симметрии говорит: радиус палиндрома
P[i]будет как минимум равен радиусу зеркального братаP[i'], но ограничен расстоянием отiдо правой границыR. Мы сразу записываем это значение вP[i], мгновенно экономя сотни проверок! - И только после этого "читерского" старта, алгоритм пытается расширить палиндром вокруг
iнаивным способом, посимвольно сравнивая края. Если новый палиндром вылезает за старую правую границуR, мы обновляемCиR.
Удивительным образом, несмотря на вложенный цикл (while для расширения краев), внутренний цикл всегда сдвигает правую границу R только вперед. Переменная R никогда не уменьшается и может увеличиться максимум N раз. Это обеспечивает алгоритму Манакера абсолютную асимптотическую сложность O(N).