Алгоритм поиска возможных палиндромных строк в списке, содержащем список возможных подпоследовательностей

#python #algorithm

#python #алгоритм

Вопрос:

У меня есть «n» количество строк в качестве входных данных, которые я разделяю на возможные подпоследовательности в список, как показано ниже

Если входные данные: aa, b, aa

Я создаю список, подобный приведенному ниже (каждый список имеет подпоследовательности строки):

 aList = [['a', 'a', 'aa'], ['b'], ['a', 'a', 'aa']]
 

Я хотел бы найти комбинации палиндромов по спискам в aList.
Например, возможными палиндромами для этого будут 5 — aba, aba, aba, aba, aabaa

Это может быть достигнуто с помощью алгоритма грубой силы с использованием приведенного ниже кода:

 d = []
def isPalindrome(x):
    if x == x[::-1]: return True
    else: return False
for I in itertools.product(*aList):
    a = (''.join(I))
    if isPalindrome(a):
        if a not in d: 
            d.append(a)
        count  = 1
 

Но этот подход приводит к таймауту, когда количество строк и длина строки больше.

Есть ли лучший подход к проблеме?

Комментарии:

1. Это d список? Замените его на set . Вероятно, вы тратите большую часть своего времени на поиск d

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

3. Очень медленно проверять, находится ли элемент в очень большом списке, но это довольно быстро сделать для набора

4. @PatrickHaugh Я попытался изменить список на набор, но все равно получаю ту же ошибку тайм-аута для больших входных данных.

5. Поскольку у вас есть судейский оракул, это домашнее задание или соревнование? Я нашел несколько ссылок, погуглив «палиндром подпоследовательности строк». Некоторые находят самые длинные, а некоторые находят все.

Ответ №1:

Вторая версия

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

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

 import itertools

aList = [['a', 'a', 'aa'], ['b'], ['a', 'a', 'aa']]

d = []
seen = set()
for I in itertools.product(*aList):
    if I not in seen:
        seen.add(I)
        a = ''.join(I)
        if a == a[::-1]:
            d.append(a)

print('d: {}'.format(d))
 

Комментарии:

1. Я попробовал использовать Set вместо list, я все еще получаю ту же ошибку тайм-аута.

Ответ №2:

Текущий подход имеет недостаток и заключается в том, что большинство сгенерированных решений в конечном итоге отбрасываются, когда проверяется, что решение является / не является палиндромным.

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

Например, допустим, что ваше пространство такое

 [["a","b","c"], ... , ["b","c","d"]]
 

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

Ответ №3:

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

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

В зависимости от того, какое из двух слов было длиннее, вы бы сравнили оставшиеся буквы со словами из следующего слева или справа массива.

Это должно привести к значительной ранней обрезке в дереве поиска. Таким образом, вы не выполнили бы полное декартово произведение комбинаций.

Я также написал функцию для получения всех подстрок из заданного слова, которые у вас, вероятно, уже были:

 def allsubstr(str):
    return [str[i:j 1] for i in range(len(str)) for j in range(i, len(str))]

def getpalindromes_trincot(aList):

    def collectLeft(common, needle, i, j):
        if i > j:
            return [common   needle   common[::-1]] if needle == needle[::-1] else []
        results = []
        for seq in aRevList[j]:
            if seq.startswith(needle):
                results  = collectRight(common needle, seq[len(needle):], i, j-1)
            elif needle.startswith(seq):
                results  = collectLeft(common seq, needle[len(seq):], i, j-1)
        return results

    def collectRight(common, needle, i, j):
        if i > j:
            return [common   needle   common[::-1]] if needle == needle[::-1] else []
        results = []
        for seq in aList[i]:
            if seq.startswith(needle):
                results  = collectLeft(common needle, seq[len(needle):], i 1, j)
            elif needle.startswith(seq):
                results  = collectRight(common seq, needle[len(seq):], i 1, j)
        return results

    aRevList = [[seq[::-1] for seq in seqs] for seqs in aList]
    return collectRight('', '', 0, len(aList)-1)

# sample input and call:
input = ['already', 'days', 'every', 'year', 'later'];
aList = [allsubstr(word) for word in input]
result = getpalindromes_trincot(aList)
 

Я провел сравнение времени с решением, опубликованным Мартино. Для примеров данных, которые я использовал, это решение примерно в 100 раз быстрее:

Посмотрите, как он выполняется на repl.it

Еще одна оптимизация

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