Сколько палиндромов можно образовать путем выбора символов из строки?

Я отправляю это от имени друга, так как считаю это довольно интересным:

Возьмите строку «abb». Если исключить любое количество букв, меньшее длины строки, мы получим 7 строк.

a b b ab ab bb abb

Из них 4 - палиндромы.

Аналогично для строки

"hihellolookhavealookatthispalindromexxqwertyuiopasdfghjklzxcvbnmmnbvcxzlkjhgfdsapoiuytrewqxxsoundsfamiliardoesit"

(длина строки 112) 2 ^ 112 - может быть сформирована 1 строка.

Сколько из них палиндромов ??

Ниже представлена ​​его реализация (хотя и на C ++, C тоже подойдет). Это довольно медленно с очень длинными словами; он хочет знать, какой самый быстрый алгоритм возможен для этого (и мне тоже любопытно: D).

#include <iostream>
#include <cstring>

using namespace std;



void find_palindrome(const char* str, const char* max, long& count)
{
    for(const char* begin = str; begin < max; begin++) {
        count++;
        const char* end = strchr(begin + 1, *begin);
        while(end != NULL) {
            count++;
            find_palindrome(begin + 1, end, count);
            end = strchr(end + 1, *begin);
        }
    }
}


int main(int argc, char *argv[])
{
    const char* s = "hihellolookhavealookatthis";
    long count = 0;

    find_palindrome(s, strlen(s) + s, count);

    cout << count << endl;
}

person Thomas Bonini    schedule 09.01.2010    source источник
comment
У старого (думаю, amiga против c64) шведского журнала Datormagazinet, в котором ежемесячно проводились соревнования по программированию, однажды была эта задача.   -  person Viktor Sehr    schedule 09.01.2010
comment
Почему этот вопрос о C, C ++ ??   -  person Mahesh Gupta    schedule 10.01.2010


Ответы (8)


Во-первых, решение вашего друга, похоже, содержит ошибку, поскольку strchr может выполнять поиск за пределами max. Даже если вы исправите это, решение будет экспоненциальным во времени.

Для более быстрого решения вы можете использовать динамическое программирование, чтобы решить эту проблему за время O (n ^ 3). . Для этого потребуется O (n ^ 2) дополнительной памяти. Обратите внимание, что для длинных строк даже 64-битных int, которые я использовал здесь, будет недостаточно для хранения решения.

#define MAX_SIZE 1000
long long numFound[MAX_SIZE][MAX_SIZE]; //intermediate results, indexed by [startPosition][endPosition]

long long countPalindromes(const char *str) {
    int len = strlen(str);
    for (int startPos=0; startPos<=len; startPos++)
        for (int endPos=0; endPos<=len; endPos++)
            numFound[startPos][endPos] = 0;

    for (int spanSize=1; spanSize<=len; spanSize++) {
        for (int startPos=0; startPos<=len-spanSize; startPos++) {
            int endPos = startPos + spanSize;
            long long count = numFound[startPos+1][endPos];   //if str[startPos] is not in the palindrome, this will be the count
            char ch = str[startPos];

            //if str[startPos] is in the palindrome, choose a matching character for the palindrome end
            for (int searchPos=startPos; searchPos<endPos; searchPos++) {
                if (str[searchPos] == ch)
                    count += 1 + numFound[startPos+1][searchPos];
            }

            numFound[startPos][endPos] = count;
        }
    }
    return numFound[0][len];
}

Объяснение:

Массив numFound[startPos][endPos] будет содержать количество палиндромов, содержащихся в подстроке с индексами от startPos до endPos.

Мы перебираем все пары индексов (startPos, endPos), начиная с коротких интервалов и заканчивая более длинными. Для каждой такой пары есть два варианта:

  1. Персонажа в str[startPos] нет на палиндроме. В этом случае существует numFound[startPos+1][endPos] возможных палиндромов - число, которое мы уже вычислили.

  2. символ в str[startPos] находится в палиндроме (в его начале). Мы просматриваем строку, чтобы найти соответствующий символ, который нужно поместить в конец палиндрома. Для каждого такого символа мы используем уже вычисленные результаты в numFound, чтобы найти количество возможностей для внутреннего палиндрома.

ИЗМЕНИТЬ:

  • Уточнение: когда я говорю «количество палиндромов, содержащихся в строке», это включает в себя несмежные подстроки. Например, палиндром «aba» содержится в «abca».

  • Можно уменьшить использование памяти до O (n), воспользовавшись тем фактом, что для вычисления numFound[startPos][x] требуется только знание numFound[startPos+1][y] для всех y. Я не буду здесь этого делать, так как это немного усложняет код.

  • Предварительная генерация списков индексов, содержащих каждую букву, может ускорить внутренний цикл, но в целом он все равно будет O (n ^ 3).

person interjay    schedule 09.01.2010
comment
@Pavel Shved: Вы можете пояснить, что имеете в виду? Мой ответ дает те же результаты, что и исходный код, после исправления упомянутой ошибки. - person interjay; 09.01.2010
comment
Боюсь, вы немного ошибаетесь в использовании субиндексов. Лично я бы сказал, что пропуск любого количества букв означает, что из abc получится a b c ab ac bc abc. Фактический пример здесь немного шаткий (с использованием abb), но вы заметите, что в производном списке ab появляется дважды, тогда как если бы строки были смежными, вы бы получили только a b b ab bb abb. - person Matthieu M.; 09.01.2010
comment
@Matthieu M .: Мой ответ не касается только смежных подстрок - он делает именно то, что задает вопрос. Например, использование строки aaa даст результат 7, а не 6, как если бы она учитывала только смежные подстроки. Если вы считаете иначе, приведите пример строки, в которой мой ответ дает неверный результат. - person interjay; 09.01.2010
comment
Приношу свои извинения, я ошибся из-за использования непрерывных промежутков. - person Matthieu M.; 09.01.2010

У меня есть способ сделать это за время O (N ^ 2) и пространство O (1), однако я думаю, что должны быть другие способы лучше.

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

    int count_palindromic_slices(const string &S) {
        int count = 0;

        for (int position=0; position<S.length(); position++) {
            int offset = 0;

            // Check the "aa" situation
            while((position-offset>=0) && (position+offset+1)<S.length() && (S.at(position-offset))==(S.at(position+offset+1))) {
                count ++;
                offset ++;
            }

            offset = 1;  // reset it for the odd length checking
            // Check the string for "aba" situation
            while((position-offset>=0) && position+offset<S.length() && (S.at(position-offset))==(S.at(position+offset))) {
                count ++;
                offset ++;
            }
        }
        return count;
    }

14 июня 2012 г. После некоторого расследования я считаю, что это лучший способ сделать это. быстрее принятого ответа.

person StanleyZ    schedule 11.02.2011
comment
Этот ответ неверен, поэтому его относительная скорость не имеет значения. count_palindromic_spaces("abb") возвращает 1, когда в вопросе говорится, что он должен вернуть 4. (Ответ для строки длиной n всегда должен быть не менее n.) Он возвращает 3 для aaa, когда должен return 7. Есть две проблемы с кодом. Во-первых, он требует, чтобы палиндромы имели минимальную длину 2, тогда как она должна быть 1. Во-вторых, он проверяет только непрерывные строки символов, когда он должен проверять все выбранные символы 2 ^ n-1. - person Rob Kennedy; 09.08.2013
comment
Привет @RobKennedy! Спасибо, что указали на ошибку в моем коде. Что касается первого, я понимаю и согласен с вами, в этом посте должен учитываться каждый персонаж. это может быть легко исправить с помощью count ++ в каждом разделе for. Для 2-го я не уверен, что получил. не могли бы вы объяснить больше? например почему ааа должно быть 7? Я думал, что должно быть 6 (a, a, a, aa, aa, aaa) даже в оригинальном внимании поста? - person StanleyZ; 14.08.2013
comment
Есть семь различных вариантов выбора символов из трехсимвольной строки (потому что 7 = 2 ^ 3-1). Трудно проиллюстрировать, когда все буквы одинаковые, поэтому давайте вместо этого посмотрим на abc. Вот семь строк: abc, ab, ac, bc, a, b, c. Ваш алгоритм упускает из виду ac. Когда все буквы совпадают, очевидно, что все эти семь строк являются палиндромами, поэтому результат count_palindromic_slices("aaa") должен быть равен 7. - person Rob Kennedy; 14.08.2013

Есть ли какой-нибудь пробег в выполнении начального обхода и построении индекса всех вхождений каждого символа.

 h = { 0, 2, 27}
 i = { 1, 30 }
 etc.

Теперь, работая слева, h, только возможные палидромы находятся в точках 3 и 17, есть ли у char [0 + 1] == char [3 -1] и т. Д. Палиндром. действительно ли char [0 + 1] == char [27 -1] нет, Дальнейший анализ char [0] не требуется.

Перейдите к char [1], нужно только, например, char [30 -1] и внутрь.

Затем, вероятно, можно поумнеть, когда вы определили палиндром, идущий из позиции x-> y, все внутренние подмножества являются известными палиндромами, поэтому мы имели дело с некоторыми элементами, можем исключить эти случаи из более позднего исследования.

person djna    schedule 09.01.2010

Мое решение с использованием O(n) памяти и O(n^2) времени, где n - длина строки:

palindrome.c:

#include <stdio.h>
#include <string.h>

typedef unsigned long long ull;

ull countPalindromesHelper (const char* str, const size_t len, const size_t begin, const size_t end, const ull count) {
  if (begin <= 0 || end >= len) {
    return count;
  }
  const char pred = str [begin - 1];
  const char succ = str [end];
  if (pred == succ) {
    const ull newCount = count == 0 ? 1 : count * 2;
    return countPalindromesHelper (str, len, begin - 1, end + 1, newCount);
  }
  return count;
}

ull countPalindromes (const char* str) {
  ull count = 0;
  size_t len = strlen (str);
  size_t i;
  for (i = 0; i < len; ++i) {
    count += countPalindromesHelper (str, len, i, i, 0);  // even length palindromes
    count += countPalindromesHelper (str, len, i, i + 1, 1); // odd length palindromes
  }
  return count;
}

int main (int argc, char* argv[]) {
 if (argc < 2) {
  return 0;
 }
 const char* str = argv [1];
 ull count = countPalindromes (str);
 printf ("%llu\n", count);
 return 0;
}

Использование:

$ gcc palindrome.c -o palindrome
$ ./palindrome myteststring

РЕДАКТИРОВАТЬ: Я неправильно интерпретировал проблему как версию проблемы с непрерывной подстрокой. Теперь, учитывая, что кто-то хочет найти количество палиндромов для несмежной версии, я сильно подозреваю, что можно было бы просто использовать математическое уравнение для его решения, учитывая количество различных символов и их соответствующее количество символов.

person Thomas Eding    schedule 09.01.2010
comment
Как вы сказали, это находит только смежные подстроки. Я сомневаюсь, что вы сможете найти математическое уравнение, чтобы перейти от этого к правильному решению, как вы сказали: например, строки abcdabcd и abcdabdc дают 8 в вашем решении, и обе имеют одинаковое количество символов. Однако правильное решение различно для обоих (24 и 27 соответственно). - person interjay; 09.01.2010
comment
О, я вижу. Я ошибочно принял значение слова «несмежный» как «полностью свободный от переупорядочивания». IOW, любая подстановка будет игрой. - person Thomas Eding; 09.01.2010

Хммммм, думаю, я бы посчитал так:

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

Кажется, это именно то, что вы делаете, хотя я не уверен, что вы не учитываете дважды крайние случаи с повторяющимися символами.

Так что, по сути, я не могу придумать лучшего способа.

РЕДАКТИРОВАТЬ:
Подумав еще немного, это можно улучшить с помощью кеширования, потому что вы иногда подсчитываете палиндромы в одной и той же подстроке более одного раза. Итак, я полагаю, это демонстрирует, что определенно есть способ лучше.

person James    schedule 09.01.2010
comment
Но strchr () стоит дорого. Работая от более мелких палиндромов наружу, необходимо сравнивать только первый и последний символы, останавливаясь на первом несовпадении. - person Adam Liss; 09.01.2010
comment
Не знаю: O (n) - не самая дорогая операция в мире. Я допускаю, что, вероятно, есть лучшее решение, но я не уверен, что оно исходит от подхода сверху вниз или снизу вверх. - person James; 09.01.2010

Вот программа для поиска всех возможных палиндромов. в строке, написанной как на Java, так и на C ++.

person bragboy    schedule 10.03.2010
comment
Это действительно программы для поиска палиндромов, но они не программы для поиска всех палиндромов, о которых спрашивает этот вопрос. - person Rob Kennedy; 09.08.2013

Я не уверен, но вы можете попробовать Фурье. Эта проблема напомнила мне об этом: Алгоритм O (nlogn) - Найдите три одинаковых строки в двоичной строке

Только мои 2 цента

person Luka Rahne    schedule 09.01.2010

person    schedule
comment
1. Вам не нужно взаимодействовать со всем словом (с while (end >= start)), а только с его половиной, поскольку вы уже сравнили вторую половину ... 2. Ваш пост вообще не отвечает на вопрос ... - person Kyle_the_hacker; 09.08.2013