在Python中查找字符串中最长的回文

时间:2017-11-12 21:39:34

标签: python algorithm

我试图解决LeetCode上的Longest Palindromic Substring问题。问题陈述是:

  

给定一个字符串s,找到s中最长的回文子字符串。您可以假设s的最大长度为1000。

     

示例:

Input: "babad"

Output: "bab"
  

注意:" aba"也是一个有效的答案。   例如:

Input: "cbbd"

Output: "bb"

我提出了以下解决方案(包括一些测试用例):

import pytest

class Solution:
    def longestPalindrome(self, s):
        candidate = ""
        longest = ""
        contains_palindrome = False
        for i, char in enumerate(s):
            if i == 0:
                candidate = char
            elif i == 1:
                if s[1] == s[0]:
                    candidate = self.get_palindrome(s, start=0, end=1)
            elif i >= 2:
                if char == s[i-1]:
                    candidate = self.get_palindrome(s, start=i-1, end=i)
                elif char == s[i-2]:
                    candidate = self.get_palindrome(s, start=i-2, end=i)
            if len(candidate) > len(longest):
                longest = candidate
        return longest

    @staticmethod
    def get_palindrome(s, start, end):
        palindrome = s[start:end+1]
        while end < len(s) - 1:
            if s[end+1] == s[start] and Solution.all_same(palindrome):
                end += 1
                palindrome += s[end]
            else:
                break
        while (start > 0) and (end < len(s) - 1):
            start -= 1
            end += 1
            if s[start] == s[end]:
                palindrome = s[start] + palindrome + s[end]
            else:
                break
        return palindrome

    @staticmethod
    def all_same(items):
        return all(item == items[0] for item in items)


def test_1():
    assert Solution().longestPalindrome("babad") == "bab"

def test_2():
    assert Solution().longestPalindrome("cbbd") == "bb"

def test_3():
    assert Solution().longestPalindrome("abba") == "abba"

def test_4():
    assert Solution().longestPalindrome("a") == "a"

def test_5():
    assert Solution().longestPalindrome("ccc") == "ccc"

def test_6():
    assert Solution().longestPalindrome("aaaa") == "aaaa"

def test_7():
    assert Solution().longestPalindrome("aaabaaaa") == "aaabaaa"


if __name__ == "__main__":
    pytest.main([__file__])

问题是我超过了时间限制&#34;错误:

enter image description here

我的理解是这个算法的时间复杂度是O(n ^ 2),因为对于每个字符,它检查一个最多可达n个字符的回文。在LeetCode的解决方案中,还有O(n ^ 2)算法(在Java中)。

我猜测Python的时间限制有点过于严格,这比Java要慢。或者我错过了什么,我的解决方案的时间复杂度实际上大于O(n ^ 2)?

4 个答案:

答案 0 :(得分:2)

您失败的测试字符串似乎只包含一个。这是最坏的情况,它实际上是O(n³)而不是O(n²),因为all_same中有另一个隐藏的循环。 (起初,我还认为字符串上的切片运算符[:]会复制,但事实并非如此。)

您需要致电all_same,因为您在主要功能中区分了“aa”和“aba”的情况。但是您不需要在循环中执行此操作,因为您将在while的第一个get_palindrome循环中仅添加相同的字母。因此,快速解决方法是测试所有字符是否只相同一次:

    if Solution.all_same(palindrome):
        while end < len(s) - 1:
            if s[end+1] == s[start]:
                end += 1
                palindrome += s[end]
            else:
                break

现在all_same os运行在两个或三个字母的字符串上并且速度很快。

更好的解决方案根本不需要all_same。当你可以传递“b”并让该函数执行其余工作时,为什么将“aba”传递给get_palindrome

        elif i >= 2:
            if char == s[i-1]:
                candidate = self.get_palindrome(s, start=i-1, end=i)
            else:
                candidate = self.get_palindrome(s, start=i, end=i)

总体而言,代码看起来相当凌乱,包含所有break和不必要的大小写区别。为什么将索引和palindrome保留为get_palindrome中的单独实体,您必须保持同步?

在我看来,这是一个更整洁的版本:

class Solution:
    def longestPalindrome(self, s):
        longest = ""

        for i, _ in enumerate(s):
            candidate = self.get_palindrome(s, start = i, end = i)

            if len(candidate) > len(longest):
                longest = candidate

        return longest

    @staticmethod
    def get_palindrome(s, start, end):
        while end + 1 < len(s) and s[end+1] == s[start]:
            end += 1

        while start > 0 and end + 1 < len(s) and s[start - 1] == s[end + 1]:
            start -= 1
            end += 1

        return s[start:end + 1]

即便如此,还有改进的余地:对于字符串“aaaa”,代码仍然会考虑“aaaa”,“aaa”,“aa”和“a”。 while中的第一个get_palindrome会一路走来,但却没有机会找到更好的打击。我们可以通过在主函数中找到相同字母的延伸来改善这一点:

class Solution:
    def longestPalindrome(self, s):
        longest = ""
        i = 0
        l = len(s)

        while i < l:
            end = i

            while end + 1 < l and s[end + 1] == s[i]:
                end += 1

            candidate = self.get_palindrome(s, i, end)

            if len(candidate) > len(longest):
                longest = candidate

            i = end + 1

        return longest

    @staticmethod
    def get_palindrome(s, start, end):
        while start > 0 and end + 1 < len(s) and s[start - 1] == s[end + 1]:
            start -= 1
            end += 1

        return s[start:end + 1]

对于像“abababab”这样的字符串,这仍然不太理想,但在你的情况下应该足够快。

答案 1 :(得分:1)

我尝试了“动态编程”的想法,找到“0阶”回文中心然后修剪,因为深度j增加并且出现不匹配

修剪是在列表comps内完成的,应该相对较快,但仍然是O(n ^ 2)

class Solution:
    def longestPalindrome(self, s):

        s = '>' + s + '<'  # add guard values

        # make lists of '0_th order' palindrome 'centers', even and odd

        evn = [i for i, a in enumerate(zip(s, s[1:])) if a[0] == a[1]]

        odd = [i + 1 for i, a in enumerate(zip(s, s[2:])) if a[0] == a[1]]

        # prune lists of centers when elements +/- j from centers don't match

        evn_last, odd_last = [[1], 0], [[1], 1]

        j = 1
        while evn:
            evn_last = (evn, j)
            evn = [e for e in evn if s[e - j] == s[e + j + 1]]
            j += 1

        j = 1
        while odd:
            odd_last = (odd, j)
            odd = [e for e in odd if s[e - j] == s[e + j]]
            j += 1

        # determine longest, construct palindrome

        if 2 * evn_last[1] > 2 * odd_last[1] - 1:

            cntr = evn_last[0][0]
            pal = s[cntr] + s[cntr + 1]
            for i in range(1, evn_last[1]):
                pal = s[cntr - i] + pal + s[cntr + i + 1]
        else:
            cntr = odd_last[0][0]
            pal = s[cntr]
            for i in range(1, odd_last[1]):
                pal = s[cntr - i] + pal + s[cntr + i]
        return pal
如果我错误地粘贴到类包装器中,我会道歉 - OOP不是我的事 确实通过了你的测试

可能已经找到了调用实例,明显重命名

S = Solution()

%timeit S.fred_longestPalindrome("aba"*300)
17.8 ms ± 230 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit S.Kurt_longestPalindrome("aba"*300)
52.8 ms ± 108 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

答案 2 :(得分:0)

这是我发现最长回文及其长度的方法。我认为这很容易理解。

首先,我将单词添加到char数组中,然后我将第一个字母与所有其他字母一起向后检查。然后像这样移动到下一个字符。使用if else和for循环获取答案,最后使用哈希集,我得到了最长的回文。

public static void main(String[] args) {

    Scanner in = new Scanner(System.in);

System.out.println(longestPalSubstr(in.nextLine().toLowerCase()));


}




static String longestPalSubstr(String str) {

       char [] input = str.toCharArray();
       Set<CharSequence> out = new HashSet<CharSequence>();

      int n1 = str.length()-1;


      for(int a=0;a<=n1;a++)
       {
          for(int m=n1;m>a;m--)
          {

          if(input[a]==input[m])
          {

           String nw = "",nw2="";

           for (int y=a;y<=m;y++)
           {

                nw=nw+input[y];
           }
           for (int t=m;t>=a;t--)
           {

               nw2=nw2+input[t];
           }


           if(nw2.equals(nw))
           {

                out.add(nw);


               break;
           }
       }

     }

   }


    int a = out.size();
    int maxpos=0;
    int max=0;
    Object [] s = out.toArray();

    for(int q=0;q<a;q++)
    {

        if(max<s[q].toString().length())
        {
            max=s[q].toString().length();
            maxpos=q;
        }
    }


   String output = "longest palindrome is : "+s[maxpos].toString()+" and the lengths is : "+ max; 
   return output;



}

此方法将返回最大长度回文及其长度。这是我尝试并获得答案的一种方式。并且此方法无论其是奇数长度还是偶数长度都将运行。

答案 3 :(得分:0)

class Solution:
    def longestPalindrome(self, s):   
        paliandr = ''
        len_s = len(s)


        def if_pal_singl(s,i,dabl):
            pal = s[i-dabl:i+1]
            indx_left = i-dabl
            indx_right = i
            while (indx_left-1 in range(len_s) and indx_right+1 in range(len_s)):
                indx_left -=1
                indx_right +=1
                if s[indx_left] == s[indx_right]:
                    pal = s[indx_left]+pal+s[indx_right]
                else: 
                    break
            return pal  


        dabl = 0        
        for i in range(1,len_s-1):
            if s[i] == s[i+1]:
                dabl+=1
                continue
            pal = if_pal_singl(s,i,dabl)
            dabl = 0
            if len(pal) > len(paliandr):
                paliandr = pal
        print (paliandr)
if __name__ == "__main__":
    Solution().longestPalindrome('abababab')