Оптимизация сложности времени / пространства для решения палиндромов - PullRequest
0 голосов
/ 10 июля 2020

Эта проблема недавно была задана Twitter:

Палиндром - это последовательность символов, которая читает одно и то же вперед и назад. Для данной строки s найдите самую длинную подстроку palindromi c в s.

Пример:

Input: "banana"
Output: "anana"

Input: "million"
Output: "illi"


class Solution: 
    def longestPalindrome(self, s):
      # Fill this in.
        
# Test program
s = "tracecars"
print(str(Solution().longestPalindrome(s)))
# racecar

Итак, я решил это так

class Solution: 
    def longestPalindrome(self, s):
        index = 0
        longestPalindrome = ""
        for x  in s:
            subStr = s[index + 1:]
            nextIndex = subStr.find(x)
            while nextIndex != -1:
                txt = x + subStr
                pland = txt[:nextIndex + 2]
                if self.isPalindromicSubString(pland):
                    if len(pland) > len(longestPalindrome):
                        longestPalindrome = pland
                nextIndex = subStr.find(x,nextIndex + 1)
            index = index + 1
        return longestPalindrome

    def isPalindromicSubString(self,subStr):
        index = 0
        reverseIndex = -1
        isItPalindromic = True
        for y in subStr:
            if y != subStr[reverseIndex]:
               isItPalindromic = False
            index = index + 1
            reverseIndex = reverseIndex - 1  
        return isItPalindromic

# Test program
s = "abcdef aka"
print(str(Solution().longestPalindrome(s)))
# racecar

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

Ответы [ 3 ]

1 голос
/ 11 июля 2020

Вот python реализация алгоритма Манахера из статьи в Википедии , на которую ссылается Кевин

def longest_palindrome_substring(s: str) -> str:
    # Add sentinel chars between chars of s and around s, to handle even length palindromes
    # Different outer chars to exit palindrome expanding loop without boundary checking
    # in case the entire string is a palindrome
    with_boundaries = "@|" + "|".join(s) + "|!"

    # Length of palindrome centered at each index in the new string
    palindrome_lengths = [0 for _ in with_boundaries]

    # Center of current palindrome
    center_current = 0
    # Right boundary of current palindrome
    right_current = 0

    # Track largest palindrome length and center index
    max_len = 0
    max_center = 0
    for i in range(2, len(with_boundaries) - 2):
        # If i is inside a bigger palindrome, copy the length of the mirror palindrome
        # e.g. *abacaba*
        #            ^  the second aba inside abacaba must have the same length of the first
        if i < right_current:
            center_mirror = 2 * center_current - i
            # add only the length of the mirror palindrome inside the current one
            palindrome_lengths[i] = min(right_current - i, palindrome_lengths[center_mirror])

        # Increase the length of the palindrome from the center
        while (
            with_boundaries[i + palindrome_lengths[i] + 1]
            == with_boundaries[i - (palindrome_lengths[i] + 1)]
        ):
            palindrome_lengths[i] += 1

        # Update current right boundary and current center index
        if i + palindrome_lengths[i] > right_current:
            right_current = i + palindrome_lengths[i]
            center_current = i

        # Keep track of the longest
        if palindrome_lengths[i] > max_len:
            max_len = palindrome_lengths[i]
            max_center = i
    # return from max_center - max_len to max_center + max_len, filtering out sentinel chars
    return "".join(
        c
        for c in with_boundaries[max_center - max_len : max_center + max_len + 1]
        if c not in "@|!"
    )

Хотя, похоже, у него есть квадратичная c сложность O (n ^ 2), имея while l oop внутри a для l oop, на самом деле это только O (n), как объяснено в wikipedia

1 голос
/ 10 июля 2020

Во-первых, я не знаю, почему вы для этого используете классы. Мы в python не пишем классы, если не используем OOP, которым вы не являетесь.

Intro

Я прошел через середину, это будут подстроки палиндрома * 1, и с этой середины пошли налево, пока подстрока не перестанет быть палиндромом или конец строки встречен

код

def find_longest_p(s):
    pal_start = 0; pal_end = 1 # indexes of longest
    for half_index in range(0, len(s)*2-1):
        # about 1st range bounds
        # it goes over middles of palindromes
        # which are at some char or between 2 chars
        # that char is s[half_index/2]
        #    where for odd it is s[x.5] meaning between s[x] a& s[x+1]
        
        c = b = -1 # start with value because for else is asking for b,c
        
        for b in range((half_index + half_index % 2) // 2 - 1, -1, -1):
            # starts from first index to the left, goes to 0
            # "a b c d" (str "abcd", disregard spaces)
            #  0123456  (half indexes)
            # for half_index == 4
            #    b goes -> 1 -> 0
            # for half_index == 3
            #    b goes -> 1 -> 0
            
            c = half_index - b # symetric index of b
            if c < len(s) and s[b] == s[c]: # if not, either end of string or no longer palindrome
                continue
            lenght = c - b - 1
            if lenght > pal_end - pal_start:
                pal_start = b + 1
                pal_end = c
            break
        else:
            # out of bounds to the left
            # arithmetic changes a little
            lenght = c - b + 1
            if lenght > pal_end - pal_start:
                pal_start = b
                pal_end = c + 1
    return s[pal_start : pal_end]

сложность

Кажется, что это O (n ^ 2) что на самом деле не так, поскольку в строке среднее не так много палиндромов. означает, что второй l oop в основном имеет одну итерацию взяв O (n) время сложность, и дополнительный O (1) пробел , определяющий 3 переменные и некоторые промежуточные продукты, или O (n) пробел , если вы включаете копирование и нарезку s (что неизбежно)

Сноски

* 1 середина палиндрома может быть по некоторому индексу или между двумя индексами поэтому я прошел двойные индексы коридоры будут: 0, 0,5, 1, 1,5, ... Я пошел: 0, 1, 2, 3, ...

0 голосов
/ 11 июля 2020

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

Longest First = True
len( 6) in   0.79ms = longestPalindrome(banana, True) == anana
len( 9) in   0.39ms = longestPalindrome(tracecars, True) == racecar
len(11) in   0.41ms = longestPalindrome(detartrated, True) == detartrated
len(19) in   0.59ms = longestPalindrome(saippuakivikauppias, True) == saippuakivikauppias
len(46) in  13.19ms = longestPalindrome(this is not the palindrome you are looking for, True) == oo

Longest First = False
len( 6) in   0.06ms = longestPalindrome(banana, False) == anana
len( 9) in   0.08ms = longestPalindrome(tracecars, False) == racecar
len(11) in   0.19ms = longestPalindrome(detartrated, False) == detartrated
len(19) in   0.46ms = longestPalindrome(saippuakivikauppias, False) == saippuakivikauppias
len(46) in   0.04ms = longestPalindrome(this is not the palindrome you are looking for, False) == oo

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

В версии longest_first:

Временная сложность в лучшем случае для истинного палиндрома равна O(N), поскольку регулярному выражению нужно всего лишь l oop по строке один раз для проверки.

Временная сложность в худшем случае - для непалиндрома. Основной l oop будет выполняться N / 2 раза. Сложность времени регулярного выражения на первом l oop нужно будет прочитать половину строки для исключения, затем на втором l oop проверьте две комбинации позиций: O(N/2) + O(2*(N/2-1)), O(3*(N/2-2)) и так далее. Вольфрам Альфа говорит, что это O(N^3/48 + N^2/8 + N/6) ~ = (N/3)^3 + (N/3)^2. Таким образом, общая временная сложность составляет около O((N/4)^4).

https://www.wolframalpha.com/input/?i=sum+%28m+* +% 28N% 2F2-% 28m-1% 29% 29% 29 + для + m +% 3D + 1 ... N% 2F2 .

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

Сначала кратчайший

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

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