简体   繁体   English

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

[英]Finding the longest palindrome within a string in Python

I'm trying to solve the Longest Palindromic Substring problem on LeetCode.我正在尝试解决 LeetCode 上的最长回文子串问题。 The problem statement is:问题陈述是:

Given a string s, find the longest palindromic substring in s.给定一个字符串 s,找出 s 中最长的回文子串。 You may assume that the maximum length of s is 1000.您可以假设 s 的最大长度为 1000。

Example:例子:

Input: "babad"

Output: "bab"

Note: "aba" is also a valid answer.注意:“aba”也是一个有效的答案。 Example:例子:

Input: "cbbd"

Output: "bb"

I've come up with the following solution (including some test cases):我提出了以下解决方案(包括一些测试用例):

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__])

The problem is that I get a "time limit exceeded" error:问题是我收到“超出时间限制”错误:

在此处输入图片说明

My understanding is that the time complexity of this algorithm is O(n^2), since for every character it checks for a palindrome which could be up to n characters long.我的理解是该算法的时间复杂度为 O(n^2),因为对于每个字符,它都会检查最多 n 个字符长的回文。 In LeetCode's solutions there are also O(n^2) algorithms (in Java).在 LeetCode 的解决方案中,还有 O(n^2) 算法(在 Java 中)。

I'm guessing that the time limit is a bit too stringent for Python, which is slower than Java.我猜是 Python 的时间限制有点太严格了,它比 Java 慢。 Or am I missing something and is the time complexity of my solution actually greater than O(n^2)?还是我遗漏了什么,我的解决方案的时间复杂度实际上是否大于 O(n^2)?

The test string that you failed on seems to consist of a's only.您失败的测试字符串似乎只包含 a。 That's the worst case and it is actually O(n³) rather than O(n²), because there's another hidden loop in all_same .这是最糟糕的情况,它实际上是 O(n³) 而不是 O(n²),因为在all_same还有另一个隐藏循环。 (At first, I also thought that the slice operator [:] on strings would do a copy, but that's not true.) (起初,我还认为字符串上的切片运算符[:]会复制,但事实并非如此。)

You need to call all_same , because you distinguish the cases "aa" and "aba" in your main function.您需要调用all_same ,因为您在主函数中区分了“aa”和“aba”。 But you don't need to do that in a loop, because you will be adding only the same letter over and oer in the first while loop in get_palindrome .但是您不需要在循环中执行此操作,因为您将在get_palindrome的第一个while循环中get_palindrome添加相同的字母。 A quick fix would therefore be to test whether all characters are the same only once:因此,快速解决方法是测试所有字符是否仅相同一次:

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

Now all_same os run on two- or three-letter strings and will be fast.现在all_same os 运行在两个或三个字母的字符串上并且速度会很快。

A better solution wouldn't require all_same at all.更好的解决方案根本不需要all_same Why do you pass "aba" to the get_palindrome when you could just pass "b" and let that function do the rest of the work:当您可以只传递“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)

Overall, the code looks rather untidy with all the break s and unnecessary case distinctions.总的来说,所有的break和不必要的区分大小写的代码看起来相当不整洁。 And why keep indices and palindrome as separate entities in get_palindrome , which you must keep in sync?为什么将索引和palindrome作为单独的实体保存在get_palindrome ,您必须保持同步?

Here's a version that is tidier in my opinion:这是我认为更整洁的版本:

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]

Even so, there's room for improvement: For the string "aaaa", the code will still consider "aaaa", "aaa", "aa" and "a".即便如此,仍有改进的空间:对于字符串“aaaa”,代码仍会考虑“aaaa”、“aaa”、“aa”和“a”。 The first while in get_palindrome will go all the way, but without chance to find a better hit. get_palindrome的第一个while会一路走下去,但没有机会找到更好的命中。 We can improve this by finding stretches of the same letter already in the main function:我们可以通过在 main 函数中找到相同字母的片段来改进这一点:

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]

This will still not be ideal on strings like "abababab", but should be fast enough in your case.这对于像“abababab”这样的字符串仍然不是理想的,但在你的情况下应该足够快。

I tried the 'dynamic programming' idea of finding centers of '0_th order' palindromes and then pruning as the depth j is increased and mismatches occur我尝试了“动态编程”的想法,即找到“0_th 阶”回文的中心,然后随着深度j的增加和不匹配的发生进行修剪

the pruning is done inside list comps and should be relatively fast but still O(n^2)修剪是在 list 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

apologies if I got pasting into the Class wrapper wrong - OOP's not my thing如果我错误地粘贴到 Class 包装器中,我深表歉意 - OOP 不是我的事
does pass your tests确实通过了你的测试

may have figured out calling instances, obvious renaming可能已经想通了调用实例,明显的重命名

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)

This is the way that i found to get the longest palindrome and its length.这是我发现获得最长回文及其长度的方法。 i think this is easy to understand.我认为这很容易理解。

first i add the word into a char array and then i checked first letter with all other letters in backward.首先我将这个词添加到一个字符数组中,然后我检查了第一个字母和所有其他字母的向后。 and move on to the next char like that.然后像这样继续下一个字符。 using if else and for loop to get the answer and finally using a hashset i got the longest palindrome.使用 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;



}

this method will return the max length palindrome and the length of it.此方法将返回最大长度回文及其长度。 its a way that i tried and got the answer.这是我尝试并得到答案的一种方式。 and this method will run whether its a odd length or even length.无论是奇数长度还是偶数长度,此方法都将运行。

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')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM