在 Python 中查找字符串中最长的回文
Posted
技术标签:
【中文标题】在 Python 中查找字符串中最长的回文【英文标题】:Finding the longest palindrome within a string in Python 【发布时间】:2017-11-12 21:39:34 【问题描述】:我正在尝试解决 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__])
问题是我收到“超出时间限制”错误:
我的理解是,这个算法的时间复杂度是 O(n^2),因为它会检查每个字符的回文数,最长可达 n 个字符。在 LeetCode 的解决方案中,还有 O(n^2) 算法(在 Java 中)。
我猜对Python来说时间限制有点太严格了,比Java慢。还是我遗漏了什么,我的解决方案的时间复杂度实际上是否大于 O(n^2)?
【问题讨论】:
我没有深入研究您的代码,但事实上的标准方法是使用动态编程。 我猜时间限制“知道”big-O 的工作原理,也就是说,它是渐近的东西。所以它会以某种方式估计你的解决方案需要多长时间 n,然后看看你在 100n 或类似的东西上做。并使用足够大的 n,以使设置成本无关紧要。 【参考方案1】:您失败的测试字符串似乎仅由 a 组成。这是最坏的情况,实际上是 O(n³) 而不是 O(n²),因为在 all_same
中还有另一个隐藏循环。 (一开始我还以为字符串上的切片操作符[:]
会做一个拷贝,但事实并非如此。)
您需要调用all_same
,因为您在主函数中区分了“aa”和“aba”这两种情况。但是您不需要在循环中执行此操作,因为您将只在get_palindrome
的第一个while
循环中添加相同的字母。因此,一个快速的解决方法是测试所有字符是否都相同一次:
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
s 和不必要的大小写区别,代码看起来相当凌乱。为什么要将索引和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”。 get_palindrome
中的第一个 while
将一路走下去,但没有机会找到更好的命中。我们可以通过在主函数中找到相同字母的延伸来改进这一点:
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”这样的字符串仍然不理想,但在你的情况下应该足够快。
【讨论】:
我提交了第一个解决方案并被接受。事实上,我错过了all_same
方法也是 O(n),所以我运行的是 O(n^3) 方法而不是 O(n^2) 方法。 (您的解决方案确实也更整洁;从单个字母开始扩展回文是有意义的)。【参考方案2】:
我尝试了“动态规划”的想法,即找到“0_th order”回文的中心,然后随着深度 j
的增加和不匹配的发生进行修剪
修剪是在列表组合内完成的,应该相对较快,但仍然 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)
【讨论】:
【参考方案3】:这是我发现获得最长回文及其长度的方法。我认为这很容易理解。
首先我将单词添加到一个字符数组中,然后我检查第一个字母和所有其他字母向后。然后像这样继续下一个字符。使用 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;
此方法将返回最大长度回文数及其长度。这是我尝试并得到答案的一种方式。无论是奇数还是偶数,这个方法都会运行。
【讨论】:
此方法将给出任意长度随机单词的输出。偶数或奇数。【参考方案4】: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')
【讨论】:
以上是关于在 Python 中查找字符串中最长的回文的主要内容,如果未能解决你的问题,请参考以下文章
最长(大)回文串的查找(字符串中找出最长的回文串)PHP实现