Как найти все палиндромы в тексте

Алгоритмы для поиска палиндромов

Время на прочтение
13 мин

Количество просмотров 145K

image

Сегодня я хочу вам рассказать об алгоритмах подсчёта количества палиндромов в строке: для чего это нужно, где применяется, как это быстро сделать, какие подводные камни нас ожидают и многое другое. Рассмотрим различные способы для решения данной задачи, выясним плюсы и минусы каждого способа. Эта статья будет обзорной: если я что-то не описываю здесь, то постараюсь всегда дать вам набор ссылок, где всё подробно описано и расписано. Надеюсь, что материал будет интересен как новичкам в сфере алгоритмов, так и матёрым программистам. Что же, если я смог заинтересовать вас, то прошу под кат!

Для начала стоит вспомнить, что же такое палиндром.

Палиндро́м — число (например, 404), буквосочетание, слово или текст, одинаково читающееся в обоих направлениях. Иногда палиндромом называют любой симметричный относительно своей середины набор символов.(выдержка из Википедии)

Определение очень лёгкое и понятное. Мы в данном посте будем рассматривать палиндромы-слова(поп, кок и т.д.). Но это не значит, что алгоритмы неприменимы для иных ситуаций. Просто чуть сузим область.

Для чего может потребоваться искать палиндромы?

На самом деле, не очень то и часто приходится искать палиндромы в повседневной жизни. Уж больно специфичная это задача. Если говорить о строковых алгоритмах, то мы намного чаще встречаем тот же поиск подстроки в строке, чем нахождение количества палиндромов. Поэтому и освещение в литературе намного слабее.

Одно из основных применений — спортивное/олимпиадное программирование. Там задач на такое хватает. Задач понаписывают, а участникам уже думай, каким способом это решить. Из личного опыта скажу, что мне такие задачи встречались всего пару раз, но все они были из разряда «вот тебе задача, ты не думай, а просто напиши алгоритм». То есть не интересные задачи, которые развивают просто механическую память по набиванию алгоритмов.

А более практическое применение нахождения палиндромов — это биологические алгоритмы. Согласно всё той же Википедии и ссылкам снизу Вики, палиндромность биологических соединений играет важную роль на свойствах различных биологических соединений. Я в биологии слаб, поэтому если вам интересно, то вы можете найти более подробную информацию сами.

Отлично, мы выяснили, что мы будем заниматься не совсем бесполезными делами. Давайте же уже перейдем к рассмотрению алгоритмов!

Замечание: во всех нижеприведённых алгоритмах одиночный символ не считается за палиндром. Как вы понимаете, это легко поправить, если одиночные символы тоже нужно будет учесть.

0) Самый банальный алгоритм с асимптотикой O(N^3)

bool SlowN3::isPalindrom(int leftBorder, int rightBorder)
{
    while(leftBorder <= rightBorder)
    {
        if(str[leftBorder] != str[rightBorder])
        {
            return false;
        }
        ++leftBorder;
        --rightBorder;
    }
    return true;
}

long long SlowN3::operator ()()
{
    for(int leftBorder = 0;leftBorder < str.length(); ++leftBorder)
    {
        for(int rightBorder = leftBorder + 1; rightBorder < str.length(); ++rightBorder)
        {
            if( isPalindrom(leftBorder, rightBorder) )
            {
                ++cntPalindromes;
            }
        }
    }
    return cntPalindromes;
}

Хочу предупредить сразу, что все исходники будут на C++, и это будут значимые части классов, которые нам могут быть интересны.

Алгоритм самый что ни на есть «лоб»: имеется строка, есть указатель на начало подстроки данной строки, есть указатель на конец данной подстроки. В двух вложенных циклах перебираем границы подстрок и банально проверяем, является ли наша подстрока палиндромом. Ничего сложного.

Но это всё ужасно медленно работает. Почему? Да потому что мы квадрат от N раз (N у нас будет всегда длина строки) перебираем границы текущей подстроки, а потом ещё и за O(N) в худшем случае выполняем проверку для каждой подстроки на палиндромность.

Плюсов у данного способа немного:

+ Легко реализуется. Действительно, оставить багу здесь ну очень сложно.
+ Легко читается. Вы только взглянули, и уже поняли, что да как здесь работает.
+ Малая скрытая константа. Как известно, на время работы алгоритма влияет не только асимптотическая оценка (здесь мы работаем только с худшими случаями), но и скрытая константа. У этого алгоритма скрытая константа крайне мала.

Минусы:

— Крайне малая скорость работы. Как вы можете видеть, что при строке из тысячи букв ‘a’ данный алгоритм сделает порядка 10^9 итераций, что есть очень плохо. А что если строка длиной 100000?

1) Самый банальный алгоритм с асимптотикой O(N^2)

Код:

void SlowN2::oddCount()
{
    for(int indMiddle = 0; indMiddle < str.length(); ++indMiddle)
    {
        int leftBorder = indMiddle - 1, rightBorder = indMiddle + 1;
        while(leftBorder >= 0 && rightBorder < str.length() && str[leftBorder] == str[rightBorder])
        {
            ++cntPalindromes;
            --leftBorder;
            ++rightBorder;
        }
    }
}

void SlowN2::evenCount()
{
    for(int indMiddle = 0; indMiddle < str.length(); ++indMiddle)
    {
        int leftBorder = indMiddle, rightBorder = indMiddle + 1;
        while(leftBorder >= 0 && rightBorder < str.length() && str[leftBorder] == str[rightBorder])
        {
            ++cntPalindromes;
            --leftBorder;
            ++rightBorder;
        }
    }
}

Здесь уже чуточку интереснее. У нас имеется строка, и два временных массива для палиндромов чётной и нечётной длины. А число в ячейке i массива будет хранить количество палиндромов в строке s(исходная строка) с центром в точке i. Для нечётной длины палиндрома центр находится легко, ну а для чётной мы просто чуть поиграемся с индексами и сместим чуточку центр(что видно в коде). Наш результат это есть сумма чисел из двух массивов.

Как видите, снова ничего сложного. Но работает это заметно быстрее предыдущего алгоритма. Почему? Давайте рассмотрим, как же оно всё работает.

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

А работает это быстро на обычных строках очень быстро потому, что наш второй, вложенный цикл, который бежит до тех пор, пока крайние символы равны, обрывается крайне быстро(если предположить, что палиндромов в строке много меньше общего числа подстрок).

Рассмотрим плюсы и минусы способа.

Плюсы:

+ Всё также легко кодится. Ошибиться очень сложно.
+ Малая скрытая константа
+ Хорошо ведёт себя на случайных строках

Минусы:

— Всё ещё долгое время работы

После этого способа уже голова начинает думать-думать-думать. И тут идея! А что если мы модифицируем этот способ, только будем от центрального элемента прыгать не на 1 символ с каждой итерацией, а на чуточку больше? И тут нам на помощь приходят…

2) Используем хеши!

Этот способ чуточку сложнее, поэтому сразу приведу код, а потом постараюсь обьяснить, что за магия там творится(хотя магии нет, как вы сами прекрасно знаете):

inline long long Hash::getHash(int indLeft, int indRight) const
{
    return prefixHash[indRight] - (indLeft > 0 ? prefixHash[indLeft - 1] : 0);
}

inline long long Hash::getRevHash(int indLeft, int indRight) const
{
    return suffixHash[indLeft] - (indRight < suffixHash.size() - 1 ? suffixHash[indRight + 1] : 0);
}

void Hash::PrepareSimpleNumbers()
{
    if(str.length() > simpleNumbers.size())
    {
        size_t oldSize = simpleNumbers.size();
        simpleNumbers.resize(str.length());
        simpleNumbers[0] = 1LL;
        for(int i = oldSize; i < simpleNumbers.size(); ++i)
            simpleNumbers[i] = simpleNumbers[i - 1] * SimpleBase;
    }
}

void Hash::CountingPrefixHash()
{
    prefixHash[0] = str[0];
    for(int i = 1; i < prefixHash.size(); ++i)
    {
        prefixHash[i] = prefixHash[i - 1] + str[i] * simpleNumbers[i];
    }
}

void Hash::CountingSuffixHash()
{
    suffixHash[suffixHash.size() - 1] = str[str.length() - 1];
    for(int i = (int) str.length() - 2, j = 1; i >= 0; --i, ++j)
    {
        suffixHash[i] = suffixHash[i + 1] + str[i] * simpleNumbers[j];
    }
}

bool Hash::isPalindrome(int indLeft, int indRight) const
{
    return getHash(indLeft, indRight) * simpleNumbers[prefixHash.size() - indRight - 1] ==
           getRevHash(indLeft, indRight) * simpleNumbers[indLeft];
}

void Hash::CountingOdd()
{
    for (int i = 0; i < oddCount.size(); i++)
    {
        int indLeft = 1, indRight = min(i + 1, static_cast<int> (oddCount.size() - i));
        while (indLeft <= indRight)
        {
            int middle = (indLeft + indRight) / 2;
            if (isPalindrome(i - middle + 1, i + middle - 1))
            {
                oddCount[i] = middle - 1;
                indLeft = middle + 1;
            }
            else
            {
                indRight = middle - 1;
            }
        }
    }
}

void Hash::CountingEven()
{
    for (int i = 0; i < evenCount.size(); i++)
    {
        int indLeft = 1, indRight = min(i, static_cast<int> (evenCount.size() - i));
        while (indLeft <= indRight)
        {
            int middle = (indLeft + indRight) / 2;
            if (isPalindrome(i - middle, i + middle - 1))
            {
                evenCount[i] = middle;
                indLeft = middle + 1;
            }
            else
            {
                indRight = middle - 1;
            }
        }
    }
}

long long Hash::operator()()
{
    PrepareSimpleNumbers();
    CountingPrefixHash();
    CountingSuffixHash();
    CountingOdd();
    CountingEven();
    for(int i = 0; i < str.length(); ++i)
    {
        cntPalindromes += oddCount[i] + evenCount[i];
    }
    return cntPalindromes;
}

Краткая суть данного способа: мы перебираем центральный элемент нашего палиндрома, и потом дихотомией стараемся найти наибольший радиус нашего палиндрома (под радиусом здесь понимается расстояние от центрального элемента до крайнего, если у нас палиндром чётной длины, то просто добавим чуточку игры с индексами, чтобы всё работало). Во время подбора мы должны как-то быстро сравнивать подстроки на идентичность. делаем это с помощью хешей. Асимптотика, как легко догадаться: N тратим на перебор центрального элемента, logN в худшем случае тратим на подбор радиуса палиндрома, за единицу сравниваем подстроки с помощью хешей. Итого имеем O(NlogN), что очень даже неплохо на самом деле.

Теперь чуть подробнее остановимся на данном методе.

На чём основывается наш метод? Так как мы перебираем центральный элемент, а потом дихотомией подбираем радиус наибольшего палиндрома с центром в данном элементе, то мы должны быстро брать хеш левой части нашего потенциального палиндрома и сравнивать его с хешем правой части нашего потенциального палиндрома.

Как это сделать? Давайте предварительно рассчитаем хеш для каждого префикса и суффикса исходной строки. В коде это у нас делают методы CountingPrefixHash() и CountingSuffixHash() соответственно.

С помощью методов getHash() и getRevHash() мы можем быстро получить хеши левой и правой частей рассматриваемого на данном этапе потенциального палиндрома. Тут может возникнуть вопрос, почему нельзя воспользоваться одной и той же функцией подсчёта хеша для левой и правой частей. А всё потому, что мы при проверке на палиндромность левую часть мы читаем слева направо, а вторую справа налево. И нам необходимо поддерживать хеш слева направо и справа налево.

Осталась единственная сложность: как сравнить этих 2 хеша? И эту проблему мы решаем с помощью метода isPalindrome(), который приводит хеши к одному основанию и сравнивает их.

Результатом каждой итерации у нас будет количество палиндромов с центром i. Пробегаемся 2 раза для палиндромов чётной и нечётной длины, ответ на нашу задачу есть сумма всех значений массивов oddCount и evenCount

Более подробно про данный метод вы можете почитать в этой статье.

Плюсы:

+ Асимптотически мы снизили до NlogN, что не может не радовать. Если брать только худшие случаи, то в теории когда-нибудь мы выиграем

Минусы:

— Довольно тяжело пишется. Легко посеять багу. Нужна немного теоретическая подготовка в области хешей.
— Медленно работает на случайных тестах. Вы можете сами в этом убедиться. А всё из-за большой скрытой константы и из-за того, что даже если у нас нет ни одного палиндрома с центром в данном элементе, то алгоритм сделает logN прыжков, а это всё занимает время.
— Коллизии. Как вы видите, в моём исходнике используется хеш, который обычно пишут на олимпиадах по программированию(да-да, я немного этим когда-то занимался). Так вот, на соревнованиях данный способ показывает себя хорошо. Лично у меня коллизии не случались. Но я знаю про способы спокойно данный хеш завалить(в частности способ с использованием строк Туэ-Морса). Я когда-то слышал, что есть какой-то фиббоначиевый хеш, который вроде бы не ломается на данном тесте, но информация недостоверная. Так что будьте осторожны с коллизиями.

А если мы хотим 100% решение без всякой там коллизийной поправки и ввода хеша по другому основанию и так далее?

3) Алгоритм Манакера

Код:

//Find palindroms like 2*N+1
void Manacker::PalN2()
{
    int leftBorder = 0, rightBorder = -1, tempMirror;//start digits for algortihm
    for(int i = 0; i < str.length(); ++i)
    {
        tempMirror = (i > rightBorder ? 0 : std::min(ansPalN2[leftBorder + rightBorder - i], 
                                                                            rightBorder - i)) + 1;//find mirror of current index
        while(i + tempMirror < str.length() && i - tempMirror >= 0 && 
                 str[i - tempMirror] == str[i + tempMirror])//increase our index
        {
            ++tempMirror;
        }
        ansPalN2[i] = --tempMirror;
        if(i + tempMirror > rightBorder)//try to increase our right border of palindrom
        {
            leftBorder = i - tempMirror;
            rightBorder = i + tempMirror;
        }
    }
}

void Manacker::Pal2()
{
    int leftBorder = 0, rightBorder = -1, tempMirror;
    for(int i = 0; i < str.length(); ++i)
    {
        tempMirror = (i > rightBorder ? 0 : std::min(ansPal2[leftBorder + rightBorder - i + 1],
                                                                            rightBorder - i + 1)) + 1;
        while(i + tempMirror - 1 < str.length() && 
                  i - tempMirror >= 0 && str[i - tempMirror] == str[i + tempMirror - 1])
        {
            ++tempMirror;
        }
        ansPal2[i] = --tempMirror;
        if(i + tempMirror - 1 > rightBorder)
        {
            leftBorder = i - tempMirror;
            rightBorder = i + tempMirror - 1;
        }
    }
}

Итак, мы получили с помощью хешей алгоритм за NlogN. Но хочется быстрее. Хочется чего-то линейного. И тут нам на помощь спешит Алгоритм Манакера (по ссылке вы также можете видеть реализацию алгоритма на Java), который мало того, что линеен, так ещё и обладает крайне малой скрытой константой, что положительно сказывается на его скорости работы. Подробно описывать способ я не буду, так как это получится пересказ этой замечательной ссылки (я сам учил алгоритм именно по этой ссылке). Здесь я приведу слегка пересказанное объяснение.

Алгоритм заключается в том, что мы обрабатываем строку символ за символом, поддерживая самый правый палиндром в нашей строке. И на каждой итерации смотрим, наш текущий элемент находится внутри границ самого правого палиндрома или нет. Если находится, то мы можем извлечь ответ из ранее посчитанных значений, путём нехитрых манипуляций с индексами. Если же не находится, то мы идём точно таким же алгоритмом, который описан в пункте 1) этого обзора: идём символ за символом и сравниваем зеркальные элементы относительно центра. Идём до тех пор, пока они равны. И не забываем обновить после этого границы самого правого найденного палиндрома. Это вкратце. Про некоторые подводные камни вы можете почитать в приведённой статье.

Что ещё интересного: во всех задачах, которые я решал на контестах (по олимпиадному программированию), хватало именно этого алгоритма. Очень просто пишется, если ты его писал N раз дома и уже знаешь наизусть.

Плюсы:

+ Довольно короткий код.
+ Очень быстро работает. Мало того, что асимптотика O(N), так ещё и малая скрытая константа.

Минусы:

— Идея не такая простая, чтобы самому сходу придумать данный алгоритм
— Можно запутаться во всяких индексах, если проявить невнимательность при написании кода

А есть ли другие способы решить данную задачу за линейное время?

4) Дерево палиндромов

Немного предыстории. Эту относительно новую структуру данных открыл Михаил Рубинчик rumi13 и представил её на Петрозаводских сборах. Структура крайне интересная своей с одной стороны простотой, а с другой тем, что позволяет быстро решать довольно широкий спектр задачи про палиндромы. И самое главное — позволяет довольно просто считать количество палиндромов в строке за O(N) (но само дерево палиндромов думаю придумывалось далеко не для этого, а для более серьёзных задач с палиндромами).

Код:

PalindromicTree::PalindromicTree(const std::string& s) : FindPalindrome(s)
{
    initTree();
}

PalindromicTree::~PalindromicTree()
{
    for(int i = 0;i < pullWorkNodes.size(); ++i)
    {
        delete pullWorkNodes[i];
    }
}

void PalindromicTree::initTree()
{
    root1 = new Node;
    root1->len = -1;
    root1->sufflink = root1;
    root2 = new Node;
    root2->len = 0;
    root2->sufflink = root1;
    suff = root2;
    pullWorkNodes.push_back(root1);
    pullWorkNodes.push_back(root2);
}

void PalindromicTree::findAddSuffix(Node* &cur, int pos, int& curlen)
{
    while (true)
    {
        curlen = cur->len;
        if (pos - 1 - curlen >= 0 && str[pos - 1 - curlen] == str[pos])
        {
            break;
        }
        cur = cur->sufflink;
    }
}

void PalindromicTree::makeSuffixLink(Node* &cur, int pos, int& curlen,int let)
{
    while (true)
    {
        cur = cur->sufflink;
        curlen = cur->len;
        if (pos - 1 - curlen >= 0 && str[pos - 1 - curlen] == str[pos])
        {
            suff->sufflink = cur->next[let];
            break;
        }
    }
}

void PalindromicTree::addLetter(int pos)
{
    Node* cur = suff;
    int let = str[pos] - 'a', curlen = 0;
    findAddSuffix(cur, pos, curlen);
    if (cur->next[let] != nullptr)
    {
        suff = cur->next[let];
        return;
    }
    suff = new Node;
    pullWorkNodes.push_back(suff);
    suff->len = cur->len + 2;
    cur->next[let] = suff;
    if (suff->len == 1)
    {
        suff->sufflink = root2;
        suff->num = 1;
        return;
    }
    makeSuffixLink(cur, pos, curlen, let);
    suff->num = 1 + suff->sufflink->num;
}

long long PalindromicTree::operator ()()
{
    for (int i = 0; i < str.length(); ++i)
    {
        addLetter(i);
        cntPalindromes += suff->num - 1;
    }
    return cntPalindromes;
}

Опять же, перескажу вкратце суть данного алгоритма. С более подробным разъяснением вы можете ознакомиться в этой замечательной статье.

Итак, идея дерева. В вершинах нашего дерева будут находиться только сами палиндромы: ‘a’, ‘b’, ‘aba’ и так далее. Понятно, что сам палиндромы мы не будем хранить, а просто будем хранить из какой вершины мы пришли сюда, и какой символ с двух сторон добавили. Также у нас существуют две фиктивные вершины для удобной реализации алгоритма.

Но как и в любом интересном дереве, у нас также существуют суффиксные ссылки. Суффиксная ссылка будет вести в вершину, которая также является палиндромом (ну потому что у нас в вершинах хранятся только палиндромы) и которая является наибольшим собственным суффиксом данной вершины. То есть из вершины ‘aba’ ссылка будет вести в вершину ‘a’.

Далее, мы по очереди добавляем символы в дерево по одному. И благодаря хитрому устройству дерева и рекурсивной операции добавления (а также суффиксным ссылкам, по которым осуществляется переход), мы обновляем всё дерево.

Это вкратце, подробнее почитайте информацию по ссылке выше (если не боитесь английского)

Плюсы:

+ Если Вы ранее работали с деревьями, то вам будет очень просто понять данную идею.
+ Позволяет решать большой спектр задач на палиндромы

Минусы:

— Работает медленнее, чем алгоритм Манакера.
— Можно поставить багу. Но, чисто субъективно, тут это сделать сложнее, чем в том же алгоритме Манакера.

Также стоит упомянуть, что с помощью деревьев существует ещё одно решение данной задачи. Оно заключается в использовании суффиксного дерева и быстрого алгоритма LCA(который работает за препроцессинг O(N) и ответ на запрос O(1). Алгоритм Фарах-Колтон-Бендера). Но он на практике не применяется, так как довольно сложен и у него крайне большая скрытая константа. Представляет скорее академический интерес.

Что может быть ещё интересно про алгоритмы? Правильно, потребление памяти и время работы.
На гитхабе Вы можете скачать также код для тестирования, который генерирует случайные строки и ищет в них палиндромы.

Моё тестирование показало, что ожидаемо алгоритм номер 0 работает крайне медленно. Лидером, как Вы можете догадаться, является алгоритм Манакера. Но что самое интересное: алгоритм с O(N^2) выигрывает с примерно двукратным отрывом у алгоритма с использованием хешей с O(NlogN), что ещё раз доказывает, что не асимптотикой единой меряются алгоритмы. Так происходит из-за особенностей отсечения алгоритма номер 1, и отсутствия оной в способе с хешами. Что касается дерева палиндромов, то оно проигрывает Манакеру в основном из-за операций с памятью, так как приходится выделять память под каждую новую вершину. Но если использовать, например, new с размещением, то разрыв сократится.

Память все алгоритмы потребляют линейно от размера входных данных.

На этом мы завершим наш обзор. Надеюсь, что вы почерпнули для себя хоть что-то новое и вам было хоть чуточку интересно! Все исходники вы можете найти в моём публичном репозитории на Github.

P.S.: Если вы заметили какие-то опечатки, неточности или у вас есть дополнения — прошу отписываться в комментариях и писать в ЛС.
P.P.S.: Смело задавайте вопросы в комментариях — постараюсь ответить, если хватит моей компетенции.

Полезные ссылки, которые могут быть вам интересны после прочтения данной статьи (некоторые ссылки могут повторяться, так как могли проскакивать в самой статье):

0) Что такое палиндром
1) Алгоритм Манакера: Вики, Codeforces, e-maxx
2) Немного про хеши и их применение: e-maxx, Habrahabr
3) Обсуждение про завал хешей на Codeforces: тыц
4) Строки (слова) Туэ-Морса: раз, два
5) Статьи про дерево палиндромов: хорошее описание, codeforces
6) Вот ещё цикл статей про поиск чисел-палиндромов: Хабр

Given a string of lowercase ASCII characters, find all distinct continuous palindromic sub-strings of it. 

Examples: 

Input: str = "abaaa"
Output:  Below are 5 palindrome sub-strings
a
aa
aaa
aba
b


Input: str = "geek"
Output:  Below are 4 palindrome sub-strings
e
ee
g
k

BRUTE APPROACH

Intuition:

  1. We declare a boolean 2-D array and fill it diagonally.
  2. We check for every gap.ie(0,1,2,…..)
  3. suppose gap=0., that means there is only one element and we put  true since  a single character is palindrome
  4. if gap = 1, we check whether extremes are same and if so we put true,else false;
  5. else for any other value of gap, we check if extremes are same and dp[i+1][j-1] yields true  and if so then we put true else false;
  6. at every time when a true is encountered we add the string in the set data structure.
  7. Atlast we return the ans

Implementation:

Java

import java.io.*;

import java.util.*;

class GFG {

    static int palindromeSubStrs(String str,

                                 Set<String> set)

    {

        int n = str.length();

        boolean dp[][] = new boolean[n][n];

        for (int gap = 0; gap < n; gap++) {

            for (int i = 0, j = gap; j < n; i++, j++) {

                if (gap == 0)

                    dp[i][j] = true;

                else if (gap == 1)

                    dp[i][j]

                        = str.charAt(i) == str.charAt(j);

                else

                    dp[i][j]

                        = (str.charAt(i) == str.charAt(j)

                           && dp[i + 1][j - 1]);

                if (dp[i][j])

                    set.add(str.substring(i, j + 1));

            }

        }

        return set.size();

    }

    public static void main(String[] args)

    {

        String str = "abaaa";

        Set<String> set = new TreeSet<>();

        palindromeSubStrs(str, set);

        System.out.println(

            "No of distinct palindromic substrings are : "

            + palindromeSubStrs(str, set));

        for (String s : set)

            System.out.println(s);

    }

}

Output

No of distinct palindromic substrings are : 5
a
aa
aaa
aba
b

Time Complexity: O(N^2 * log(N)) since we are iterating through the matrix and while doing so we put elements in set which takes log(N) time

Space Complexity: O(N^2) since we using 2-D array

Method 1:

Step 1: Finding all palindromes using modified Manacher’s algorithm: 
Considering each character as a pivot, expand on both sides to find the length of both even and odd length palindromes centered at the pivot character under consideration and store the length in the 2 arrays (odd & even). 
Time complexity for this step is O(n^2)

Step 2: Inserting all the found palindromes in a HashMap: 
Insert all the palindromes found from the previous step into a HashMap. Also insert all the individual characters from the string into the HashMap (to generate distinct single letter palindromic sub-strings). 
Time complexity of this step is O(n^3) assuming that the hash insert search takes O(1) time. Note that there can be at most O(n^2) palindrome sub-strings of a string. In below C++ code ordered hashmap is used where the time complexity of insert and search is O(Logn). In C++, ordered hashmap is implemented using Red Black Tree.

Step 3: Printing the distinct palindromes and number of such distinct palindromes: 
The last step is to print all values stored in the HashMap (only distinct elements will be hashed due to the property of HashMap). The size of the map gives the number of distinct palindromic continuous sub-strings.

Below is the implementation of the above idea.

C++

#include <iostream>

#include <map>

using namespace std;

void palindromeSubStrs(string s)

{

    map<string, int> m;

    int n = s.size();

    int R[2][n+1];

    s = "@" + s + "#";

    for (int j = 0; j <= 1; j++)

    {

        int rp = 0;  

        R[j][0] = 0;

        int i = 1;

        while (i <= n)

        {

            while (s[i - rp - 1] == s[i + j + rp])

                rp++; 

            R[j][i] = rp;

            int k = 1;

            while ((R[j][i - k] != rp - k) && (k < rp))

            {

                R[j][i + k] = min(R[j][i - k],rp - k);

                k++;

            }

            rp = max(rp - k,0);

            i += k;

        }

    }

    s = s.substr(1, n);

    m[string(1, s[0])]=1;

    for (int i = 1; i <= n; i++)

    {

        for (int j = 0; j <= 1; j++)

            for (int rp = R[j][i]; rp > 0; rp--)

               m[s.substr(i - rp - 1, 2 * rp + j)]=1;

        m[string(1, s[i])]=1;

    }

   cout << "Below are " << m.size()-1

        << " palindrome sub-strings";

   map<string, int>::iterator ii;

   for (ii = m.begin(); ii!=m.end(); ++ii)

      cout << (*ii).first << endl;

}

int main()

{

    palindromeSubStrs("abaaa");

    return 0;

}

Java

import java.util.Map;

import java.util.TreeMap;

public class GFG

{    

    static void palindromeSubStrs(String s)

    {

        TreeMap<String , Integer> m = new TreeMap<>();

        int n = s.length();

        int[][] R = new int[2][n+1];

        s = "@" + s + "#";

        for (int j = 0; j <= 1; j++)

        {

            int rp = 0;  

            R[j][0] = 0;

            int i = 1;

            while (i <= n)

            {

                while (s.charAt(i - rp - 1) == s.charAt(i +

                                                j + rp))

                    rp++; 

                R[j][i] = rp;

                int k = 1;

                while ((R[j][i - k] != rp - k) && (k < rp))

                {

                    R[j][i + k] = Math.min(R[j][i - k],

                                              rp - k);

                    k++;

                }

                rp = Math.max(rp - k,0);

                i += k;

            }

        }

        s = s.substring(1, s.length()-1);

        m.put(s.substring(0,1), 1);

        for (int i = 1; i < n; i++)

        {

            for (int j = 0; j <= 1; j++)

                for (int rp = R[j][i]; rp > 0; rp--)

                   m.put(s.substring(i - rp - 1,  i - rp - 1

                                       + 2 * rp + j), 1);

            m.put(s.substring(i, i + 1), 1);

        }

       System.out.println("Below are " + (m.size())

                           + " palindrome sub-strings");

       for (Map.Entry<String, Integer> ii:m.entrySet())

          System.out.println(ii.getKey());

    }

    public static void main(String args[])

    {

        palindromeSubStrs("abaaa");

    }

}

Python3

def palindromeSubStrs(s):

    m = dict()

    n = len(s)

    R = [[0 for x in range(n+1)] for x in range(2)]

    s = "@" + s + "#"

    for j in range(2):

        rp = 0   

        R[j][0] = 0

        i = 1

        while i <= n:

            while s[i - rp - 1] == s[i + j + rp]:

                rp += 1

            R[j][i] = rp

            k = 1

            while (R[j][i - k] != rp - k) and (k < rp):

                R[j][i+k] = min(R[j][i-k], rp - k)

                k += 1

            rp = max(rp - k, 0)

            i += k

    s = s[1:len(s)-1]

    m[s[0]] = 1

    for i in range(1,n):

        for j in range(2):

            for rp in range(R[j][i],0,-1):

                m[s[i - rp - 1 : i - rp - 1 + 2 * rp + j]] = 1

        m[s[i]] = 1

    print ("Below are " + str(len(m)) + " pali sub-strings")

    for i in m:

        print (i)

palindromeSubStrs("abaaa")

C#

using System;

using System.Collections.Generic;

class GFG

{

    public static void palindromeSubStrs(string s)

    {

        Dictionary < string,

        int > m = new Dictionary < string,

        int > ();

        int n = s.Length;

        int[, ] R = new int[2, n + 1];

        s = "@" + s + "#";

        for (int j = 0; j <= 1; j++)

        {

            int rp = 0;

            R[j, 0] = 0;

            int i = 1;

            while (i <= n)

            {

                while (s[i - rp - 1] == s[i + j + rp])

                rp++;

                R[j, i] = rp;

                int k = 1;

                while ((R[j, i - k] != rp - k) && k < rp)

                {

                    R[j, i + k] = Math.Min(R[j, i - k], rp - k);

                    k++;

                }

                rp = Math.Max(rp - k, 0);

                i += k;

            }

        }

        s = s.Substring(1);

        if (!m.ContainsKey(s.Substring(0, 1)))

            m.Add(s.Substring(0, 1), 1);

        else

            m[s.Substring(0, 1)]++;

        for (int i = 1; i < n; i++)

        {

            for (int j = 0; j <= 1; j++)

            for (int rp = R[j, i]; rp > 0; rp--)

            {

                if (!m.ContainsKey(s.Substring(i - rp - 1, 2 * rp + j)))

                    m.Add(s.Substring(i - rp - 1, 2 * rp + j), 1);

                else

                    m[s.Substring(i - rp - 1, 2 * rp + j)]++;

            }

            if (!m.ContainsKey(s.Substring(i, 1)))

                m.Add(s.Substring(i, 1), 1);

            else

                m[s.Substring(i, 1)]++;

        }

        Console.WriteLine("Below are " + (m.Count));

        foreach(KeyValuePair < string, int > ii in m)

        Console.WriteLine(ii.Key);

    }

    public static void Main(string[] args)

    {

        palindromeSubStrs("abaaa");

    }

}

Javascript

<script>

function palindromeSubStrs(s)

{

    let m = new Map();

    let n = s.length;

    let R = new Array(2);

    for(let i = 0; i < 2; i++)

        R[i] = new Array(n + 1);

    s = "@" + s + "#";

    for (let j = 0; j <= 1; j++)

    {

        let rp = 0;  

        R[j][0] = 0;

        let i = 1;

        while (i <= n)

        {

            while (s[i - rp - 1] == s[i + j + rp])

                rp++; 

            R[j][i] = rp;

            let k = 1;

            while ((R[j][i - k] != rp - k) && (k < rp))

            {

                R[j][i + k] = Math.min(R[j][i - k],rp - k);

                k++;

            }

            rp = Math.max(rp - k,0);

            i += k;

        }

    }

    s = s.substring(1, n+1);

    m.set(`${s[0]}`,1);

    for (let i = 1; i < n; i++)

    {

        for (let j = 0; j <= 1; j++){

            for (let rp = R[j][i]; rp > 0; rp--){

               m.set(s.substring(i - rp - 1, i + rp + j-1),1);

            }

        }

        m.set(`${s[i]}`,1);

    }

   document.write(`Below are ${m.size} palindrome sub-strings`,"</br>");

   for(let [x, y] of m)

      document.write(x,"</br>");

}

palindromeSubStrs("abaaa");

</script>

Output: 

 Below are 5 palindrome sub-strings
a
aa
aaa
aba
b 

Method 2 :

String length – N

Step 1 : Find all the palindromic sub-strings

        First for every sub-string check if it is palindrome or not using dynamic programming like this – https://www.geeksforgeeks.org/count-palindrome-sub-strings-string/

        Time complexity – O(N2)   and   Space complexity – O(N2)

Step 2 : Remove duplicate palindromes

        For every index starting from index 0 we will use KMP algorithm and check if prefix and suffix is same and is palindrome then we will put 0 the dp array for that suffix sub-string

        Time complexity O(N2)    and   Space complexity O(N) for KMP array

Step 3 : Print the distinct palindromes and number of such palindromes

        For every sub-string check if it is present in dp array (i.e dp[i][j] == true) and print it.

        Time complexity O(N2)   and    Space complexity O(N)

Overall Time complexity – O(N2)

Overall Space complexity – O(N2)

Below is the implementation of the above idea.

C++

#include <iostream>

#include <vector>

using namespace std;

int solve(string s)

{

    int n = s.size();

    vector<vector<bool> > dp(n, vector<bool>(n, false));

    for (int i = 0; i < n; i++) {

        dp[i][i] = 1;

        if (i < n && s[i] == s[i + 1]) {

            dp[i][i + 1] = 1;

        }

    }

    for (int len = 3; len <= n; len++) {

        for (int i = 0; i + len - 1 < n; i++) {

            if (s[i] == s[i + (len - 1)]

                && dp[i + 1][i + (len - 1) - 1]) {

                dp[i][i + (len - 1)] = true;

            }

        }

    }

    vector<int> kmp(n, 0);

    for (int i = 0; i < n; i++) {

        int j = 0, k = 1;

        while (k + i < n) {

            if (s[j + i] == s[k + i]) {

                dp[k + i - j][k + i] = false;

                kmp[k++] = ++j;

            }

            else if (j > 0) {

                j = kmp[j - 1];

            }

            else {

                kmp[k++] = 0;

            }

        }

    }

    int count = 0;

    for (int i = 0; i < n; i++) {

        string str;

        for (int j = i; j < n; j++) {

            str += s[j];

            if (dp[i][j]) {

                count++;

                cout << str << 'n';

            }

        }

    }

    cout << "Total number of distinct palindromes is "

         << count << 'n';

      return 0;

}

int main()

{

    string s1 = "abaaa", s2 = "aaaaaaaaaa";

    solve(s1);

    solve(s2);

    return 0;

}

Java

import java.util.*;

class GFG

{

  public static void main(String[] args)

  {

    String s1 = "abaaa", s2 = "aaaaaaaaaa";

    solve(s1);

    solve(s2);

  }

  public static int solve(String s)

  {

    int n = s.length();

    boolean[][] dp = new boolean[n][n];

    for (int i = 0; i < n; i++)

    {

      dp[i][i] = true;

      if (i < n - 1

          && s.charAt(i) == s.charAt(i + 1)) {

        dp[i][i + 1] = true;

      }

    }

    for (int len = 3; len <= n; len++) {

      for (int i = 0; i + len - 1 < n; i++) {

        if (s.charAt(i) == s.charAt(i + (len - 1))

            && dp[i + 1][i + (len - 1) - 1]) {

          dp[i][i + (len - 1)] = true;

        }

      }

    }

    int[] kmp = new int[n];

    for (int i = 0; i < n; i++)

    {

      int j = 0, k = 1;

      while (k + i < n) {

        if (s.charAt(j + i) == s.charAt(k + i))

        {

          dp[k + i - j][k + i] = false;

          kmp[k++] = ++j;

        }

        else if (j > 0) {

          j = kmp[j - 1];

        }

        else {

          kmp[k++] = 0;

        }

      }

    }

    int count = 0;

    for (int i = 0; i < n; i++) {

      String str = "";

      for (int j = i; j < n; j++) {

        str += s.charAt(j);

        if (dp[i][j])

        {

          count++;

          System.out.println(str);

        }

      }

    }

    System.out.println(

      "Total number of distinct palindromes is "

      + count);

    return 0;

  }

}

Python3

def solve(s):

    n = len(s)

    dp = [[False for j in range(n)] for i in range(n)]

    for i in range(n):

        dp[i][i] = True

        if i < n-1 and s[i] == s[i+1]:

            dp[i][i+1] = True

    for lenk in range(3, n+1):

        for i in range(n-lenk+1):

            if s[i] == s[i+lenk-1] and dp[i+1][i+lenk-2]:

                dp[i][i+lenk-1] = True

    kmp = [0]*n

    for i in range(n):

        j, k = 0, 1

        while k+i < n:

            if s[j+i] == s[k+i]:

                dp[k+i-j][k+i] = False

                kmp[k] = j+1

                k += 1

                j += 1

            elif j > 0:

                j = kmp[j-1]

            else:

                kmp[k] = 0

                k += 1

    count = 0

    for i in range(n):

        str = ""

        for j in range(i, n):

            str += s[j]

            if dp[i][j]:

                count += 1

                print(str)

    print("Total number of distinct palindromes is", count)

    return 0

if __name__ == "__main__":

    s1 = "abaaa"

    s2 = "aaaaaaaaaa"

    solve(s1)

    solve(s2)

C#

using System;

class GFG

{

  static int solve(string s)

  {

    int n = s.Length;

    bool[][] dp = new bool[n][];

    for (int i = 0; i < n; i++)

    {

      dp[i] = new bool[n];

      dp[i][i] = true;

      if (i < n - 1 && s[i] == s[i + 1])

      {

        dp[i][i + 1] = true;

      }

    }

    for (int len = 3; len <= n; len++)

    {

      for (int i = 0; i + len - 1 < n; i++)

      {

        if (s[i] == s[i + len - 1] && dp[i + 1][i + len - 2])

        {

          dp[i][i + len - 1] = true;

        }

      }

    }

    int[] kmp = new int[n];

    for (int i = 0; i < n; i++)

    {

      int j = 0, k = 1;

      while (k + i < n)

      {

        if (s[j + i] == s[k + i])

        {

          dp[k + i - j][k + i] = false;

          kmp[k++] = ++j;

        }

        else if (j > 0)

        {

          j = kmp[j - 1];

        }

        else

        {

          kmp[k++] = 0;

        }

      }

    }

    int count = 0;

    for (int i = 0; i < n; i++)

    {

      string str = "";

      for (int j = i; j < n; j++)

      {

        str += s[j];

        if (dp[i][j])

        {

          count++;

          Console.WriteLine(str);

        }

      }

    }

    Console.WriteLine("Total number of distinct palindromes is "+ count);

    return count;

  }

  static void Main(string[] args)

  {

    string s1 = "abaaa";

    string s2 = "aaaaaaaaaa";

    solve(s1);

    solve(s2);

  }

}

Javascript

function solve(s)

{

    let n = s.length;

    let dp = new Array(n);

    for(let i = 0; i < n; i++)

        dp[i] = new Array(n).fill(0);

    for (let i = 0; i < n; i++)

    {

        dp[i][i] = 1;

        if (i < n && s[i] == s[i + 1]) {

            dp[i][i + 1] = 1;

        }

    }

    for (let len = 3; len <= n; len++) {

        for (let i = 0; i + len - 1 < n; i++) {

            if (s[i] == s[i + (len - 1)]

                && dp[i + 1][i + (len - 1) - 1]) {

                dp[i][i + (len - 1)] = true;

            }

        }

    }

    let kmp=new Array(n).fill(0);

    for (let i = 0; i < n; i++)

    {

        let j = 0, k = 1;

        while (k + i < n)

        {

            if (s[j + i] == s[k + i])

            {

                dp[k + i - j][k + i] = false;

                kmp[k++] = ++j;

            }

            else if (j > 0) {

                j = kmp[j - 1];

            }

            else {

                kmp[k++] = 0;

            }

        }

    }

    let count = 0;

    for (let i = 0; i < n; i++) {

        let str="";

        for (let j = i; j < n; j++) {

            str += s[j];

            if (dp[i][j])

            {

                count++;

                console.log(str);

            }

        }

    }

    console.log("Total number of distinct palindromes is "

        + count);

}

let s1 = "abaaa", s2 = "aaaaaaaaaa";

solve(s1);

solve(s2);

Output

a
aba
b
aa
aaa
Total number of distinct palindromes is 5
a
aa
aaa
aaaa
aaaaa
aaaaaa
aaaaaaa
aaaaaaaa
aaaaaaaaa
aaaaaaaaaa
Total number of distinct palindromes is 10

Approach:

This code uses an unordered set to store distinct palindromes and avoids using dynamic programming or the KMP algorithm. It first checks for odd-length palindromes and then even-length palindromes by expanding from each character as a center. Finally, it prints all distinct palindromes and the total count.

C++

#include <iostream>

#include <unordered_set>

using namespace std;

void findAllPalindromes(const string& s) {

    int n = s.size();

    unordered_set<string> palindromes;

    for (int i = 0; i < n; i++) {

        int left = i, right = i;

        while (left >= 0 && right < n && s[left] == s[right]) {

            palindromes.insert(s.substr(left, right - left + 1));

            left--;

            right++;

        }

    }

    for (int i = 0; i < n - 1; i++) {

        int left = i, right = i + 1;

        while (left >= 0 && right < n && s[left] == s[right]) {

            palindromes.insert(s.substr(left, right - left + 1));

            left--;

            right++;

        }

    }

    for (const auto& palindrome : palindromes) {

        cout << palindrome << endl;

    }

    cout << "Total number of distinct palindromes is " << palindromes.size() << endl;

}

int main() {

    string s1 = "abaaa", s2 = "aaaaaaaaaa";

    findAllPalindromes(s1);

    findAllPalindromes(s2);

    return 0;

}

Output

aa
aaa
aba
b
a
Total number of distinct palindromes is 5
aaaaaaaaaa
aaaaaaaa
aaaaaa
aaaa
aaaaaaa
aa
aaaaa
aaa
aaaaaaaaa
a
Total number of distinct palindromes is 10

Time complexity: O(N^2)
Space complexity: O(N)

Similar Problem: 
Count All Palindrome Sub-Strings in a String
This article is contributed by Vignesh Narayanan and Sowmya Sampath. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
 

Last Updated :
24 May, 2023

Like Article

Save Article

Дана строка, найти в ней все возможные палиндромные подстроки.

Задача отличается от задачи поиска возможной палиндромной подпоследовательности. В отличие от подпоследовательностей, подстроки должны занимать последовательные позиции в исходной строке.

 
Например,

Input:  str = google

 
Output: e l g o oo goog

Потренируйтесь в этой проблеме

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

 
Мы можем решить эту проблему в O(n2) время и O(1) пространство. Идея вдохновлена Самая длинная палиндромная подстрока проблема. Для каждого символа в данной строке считайте его серединой палиндрома и расширьте в обоих направлениях, чтобы найти все палиндромы, у которых он является средней точкой. Для палиндрома четной длины считайте каждую соседнюю пару символов средней точкой. Мы используем набор для хранения всех уникальных палиндромных подстрок.

Ниже приведена реализация этой идеи на C++, Java и Python:

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#include <iostream>

#include <string>

#include <unordered_set>

using namespace std;

// Расширяем в обоих направлениях `low` и `high`, чтобы найти все палиндромы

void expand(string str, int low, int high, auto &set)

{

    // работаем до тех пор, пока `str[low.high]` не станет палиндромом

    while (low >= 0 && high < str.length() && str[low] == str[high])

    {

        // помещаем все палиндромы в набор

        set.insert(str.substr(low, high low + 1));

        // Расширяем в обоих направлениях

        low, high++;

    }

}

// Функция для поиска всех уникальных палиндромных подстрок заданной строки

void findPalindromicSubstrings(string str)

{

    // создаем пустой набор для хранения всех уникальных палиндромных подстрок

    unordered_set<string> set;

    for (int i = 0; i < str.length(); i++)

    {

        // найти все палиндромы нечетной длины с `str[i]` в качестве средней точки

        expand(str, i, i, set);

        // найти все палиндромы четной длины с `str[i]` и `str[i+1]` как

        // его середины

        expand(str, i, i + 1, set);

    }

    // вывести все уникальные палиндромные подстроки

    for (auto i: set) {

        cout << i << » «;

    }

}

int main()

{

    string str = «google»;

    findPalindromicSubstrings(str);

    return 0;

}

Скачать  Выполнить код

результат:

e l g o oo goog

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

import java.util.HashSet;

import java.util.Set;

class Main

{

    // Расширяем в обоих направлениях `low` и `high`, чтобы найти все палиндромы

    public static void expand(String str, int low, int high, Set<String> set)

    {

        // работаем до тех пор, пока `str[low.high]` не станет палиндромом

        while (low >= 0 && high < str.length()

                && str.charAt(low) == str.charAt(high))

        {

            // помещаем все палиндромы в набор

            set.add(str.substring(low, high + 1));

            // Расширяем в обоих направлениях

            low;

            high++;

        }

    }

    // Функция для поиска всех уникальных палиндромных подстрок заданной строки

    public static void findPalindromicSubstrings(String str)

    {

        // базовый вариант

        if (str == null) {

            return;

        }

        // создаем пустой набор для хранения всех уникальных палиндромных подстрок

        Set<String> set = new HashSet<>();

        for (int i = 0; i < str.length(); i++)

        {

            // найти все палиндромы нечетной длины с `str[i]` в качестве средней точки

            expand(str, i, i, set);

            // найти все палиндромы четной длины с помощью `str[i]` и `str[i+1]`

            // как его середины

            expand(str, i, i + 1, set);

        }

        // вывести все уникальные палиндромные подстроки

        System.out.print(set);

    }

    public static void main(String[] args)

    {

        String str = «google»;

        findPalindromicSubstrings(str);

    }

}

Скачать  Выполнить код

результат:

[oo, goog, e, g, l, o]

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

# Разверните в обоих направлениях `low` и `high`, чтобы найти все палиндромы

def expand(s, low, high, palindromes):

    # выполняется до тех пор, пока `s[low.high]` не станет палиндромом

    while low >= 0 and high < len(s) and s[low] == s[high]:

        # помещает все палиндромы в набор

        palindromes.add(s[low: high + 1])

        # Расширение в обоих направлениях

        low = low 1

        high = high + 1

# Функция для поиска всех уникальных палиндромных подстрок заданной строки

def findPalindromicSubstrings(s):

    # создает пустой набор для хранения всех уникальных палиндромных подстрок

    palindromes = set()

    for i in range(len(s)):

        # найти все палиндромы нечетной длины с `s[i]` в качестве средней точки

        expand(s, i, i, palindromes)

        # найти все палиндромы четной длины с `s[i]` и `s[i+1]`

        # как его средние точки

        expand(s, i, i + 1, palindromes)

    # вывести все уникальные палиндромные подстроки

    print(palindromes, end=»)

if __name__ == ‘__main__’:

    s = ‘google’

    findPalindromicSubstrings(s)

Скачать  Выполнить код

результат:

{‘e’, ‘oo’, ‘g’, ‘l’, ‘o’}

Спасибо за чтение.

Пожалуйста, используйте наш онлайн-компилятор размещать код в комментариях, используя C, C++, Java, Python, JavaScript, C#, PHP и многие другие популярные языки программирования.

Как мы? Порекомендуйте нас своим друзьям и помогите нам расти. Удачного кодирования :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <stdio.h>
#include <ctype.h>
#include <string.h>
 
char* p_strrev(char* str);
int   p_ispoli(const char* str);
int   p_findpoli(const char* str, char* s);
void  str_erase(char* str, char* s);
void  print_poli(char* str, int ignore);
 
int  main(void) {
 
    char buf[128] = "The Super ATTA PPP, 5555, 55, 4 [repuS], adam-mada, optionals.";
//  gets(buf);
 
    print_poli(buf, 0);  // выводим список найденных палиндромов
 
    printf("src: %sn", buf);  // исходная строка
    print_poli(buf, 1); // удалить все палиндромы из строки
    printf("dst: %sn", buf);     // показать её
 
    getchar();
    return 0;
}
 
 
// реверсия строки
char* p_strrev(char* str) {
   char* s = str + (strlen(str)-1), ch, *tmp = str;
   while(str < s) {
       ch   = *str;
       *str = *s;
       *s   = ch;  
       ++str;
       --s;
   } 
   return tmp;
}
 
// сравнение на палиндром слова ABBA = true, ALKA = false
int   p_ispoli(const char* str) {
    const char* ls = str + (strlen(str)-1);
    for(; str < ls && *str == *ls; ++str, --ls);
    if(str == ls || ! ~(ls - str))
         return 1;
    return 0;
}
 
// поиск в строке палиндрома ABC = CBA
int  p_findpoli(const char* str, char* s) {
    if((str = strstr(str, s))) {
        if(! isalnum( *(str + strlen(s)) ))
                      return 1;
    }
    return 0;
}
 
 
// удаление из строки слова, str = "The ocean, ocean..." -> str_erase(str, "ocean") -> "The, ..." 
void  str_erase(char* str, char* s) {
    char* a, *b;
    int   len;
    do {
       for(a = str, b = s; *a == *b; *a++, *b++);
           if(! *b) {
                for(len = strlen(s); len; len--)
                      for(a = str, b = str + 1; *a; *a++ = *b++);
           }
    } while( *str++ );
}
 
 
// вот самая функция по выводу палиндромов ignore = 0, или удаление  палиндромов из строки ignore = 1
void  print_poli(char* str, int ignore) {
      char buf[48], *i, *src = str;
      while( *src ) {
           for(i = buf; isalnum(*src) && *src; *i++ = *src++);
           *i    = '';
           if(strlen(buf) > 1) {
                    if(p_ispoli(buf)) {
                           if(! ignore) 
                                 puts(buf);
                           else {
                                 str_erase(str, buf);
                                 src = str;
                           }
                    } else {
                        if(p_findpoli(src, p_strrev(buf))) {
                             if(! ignore) {
                                 puts(p_strrev(buf));
                                 puts(p_strrev(buf));
                             } else { 
                                 str_erase(str, buf);
                                 str_erase(str, p_strrev(buf));
                                 src = str;
                             }
                         }
                   }
             }
             for(; ! isalnum(*src) && *src; *src++);
    }
}

Содержание

  1. Что такое палиндром
  2. Проверка слова
  3. Проверка фразы

Давайте реализуем алгоритм поиска слов-палиндромов на Java.

Для начала надо определиться, что же такое палиндром? Палиндром – это слово или строка текста, которая читается слева направо и справа налево одинаково. Например, палиндромами являются такие слова как «ага», «дед», «довод», «кабак», «мадам», «шалаш». В более широком смысле число «101» также является палиндромом.

Давайте сначала рассмотрим более простой алгоритм, который на вход получает одно слово без пробелов. В случае, если данное слово палиндром – возвращаем true.

Суть алгоритма заключается в том, что мы сравниваем по два символа с обоих концов строки между собой (первый и последний, второй и предпоследний и т.д.) до тех пор, пока они равны или пока мы не дойдём до середины слова. Если в какой-то момент символы окажутся различными, то это уже точно не палиндром. Признаком того, что мы дошли до середины, является тот факт, что левый и правый индексы у нас пересекутся.

private boolean isWordPalindrome(String word) {
    var chars = word.toCharArray();
    var left = 0; // индекс первого символа
    var right = chars.length — 1; // индекс последнего символа
    while (left < right) { // пока не дошли до середины слова
        if (chars[left] != chars[right]) {
            return false;
        }
        left++;
        right—;
    }
    return true;
}

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

Затем заводим две переменных left и right для хранения левого и правого индекса соответственно.

После этого в цикле сравниваем левый и правый символы между собой до тех пор, пока левый индекс меньше правого. Если они окажутся равными или левый будет больше правого – они пересеклись.

В самом цикле проверяем равенство левого и правого символа. Если они различны – сразу возвращаем false и выходим из метода. Далее левый символ увеличиваем на 1, а правый – уменьшаем на 1.

Если мы дошли до середины строки и ни разу не встретили различий, то возвращаем true.

Теперь усложним задачу и будем проверять не отдельное слово, а целую фразу. Приведу несколько фраз-палиндромов. Попробуйте прочитать их справа налево.

  • Искать такси
  • Лидер бредил
  • А роза упала на лапу Азора
  • Уж редко рукою окурок держу

Как видите, тут уже встречаются пробелы, которые мы просто должны игнорировать. Также нам нужно сравнивать символы независимо от регистра.

Взяв за основу ранее рассмотренный метод, немного модифицируем его, преобразуя сначала все символы в нижний регистр:

private boolean isTextPalindrome(String text) {
    var chars = text.toLowerCase().toCharArray();
    var left = 0;
    var right = chars.length — 1;
    while (left < right) {
        if (chars[left] != chars[right]) {
            return false;
        }
        // …

Теперь нам нужно не просто единожды инкрементировать левый индекс, а увеличивать его до тех пор, пока он меньше правого индекса и пока левый символ будет пробелом. Для этого используем цикл с пост-условием do-while:

do {
    left++;
} while (left < right && chars[left] == ‘ ‘);

Аналогично правый индекс уменьшаем до тех пор, пока он больше левого и пока правый символ – пробел:

do {
    right—;
} while (right > left && chars[right] == ‘ ‘);

В итоге наш улучшенный метод примет следующий вид:

private boolean isTextPalindrome(String text) {
    var chars = text.toLowerCase().toCharArray();
    var left = 0;
    var right = chars.length — 1;
    while (left < right) {
        if (chars[left] != chars[right]) {
            return false;
        }

        do {
            left++;
        } while (left < right && chars[left] == ‘ ‘);

        do {
            right—;
        } while (right > left && chars[right] == ‘ ‘);
    }
    return true;
}

Помимо пробелов во фразах могут встречаться и другие символы. Например, знаки препинания. В таком случае, просто замените явную проверку на пробел на вызов метода Character.isLetterOrDigit().

Реализацию данного алгоритма у вас могут спросить на собеседовании, поэтому его полезно знать.

Понравилась статья? Поделить с друзьями:

Не пропустите также:

  • Как найти длину генетического кода
  • Как найти свой скриншот на ноутбуке
  • Пропускает ниппель на мяче как исправить
  • Как найти что ищешь заговор
  • Забыл пароль вконтакте как найти

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии