问题
I'm trying to solve the Longest Palindromic Substring problem on LeetCode. The problem statement is:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer. 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. In LeetCode's solutions there are also O(n^2) algorithms (in Java).
I'm guessing that the time limit is a bit too stringent for Python, which is slower than Java. Or am I missing something and is the time complexity of my solution actually greater than O(n^2)?
回答1:
The test string that you failed on seems to consist of a's only. That's the worst case and it is actually O(n³) rather than O(n²), because there's another hidden loop in 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. 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
. 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.
A better solution wouldn't require all_same
at all. 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:
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. And why keep indices and palindrome
as separate entities in get_palindrome
, which you must keep in sync?
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". The first while
in get_palindrome
will go all the way, but without chance to find a better hit. We can improve this by finding stretches of the same letter already in the main function:
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.
回答2:
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
the pruning is done inside list comps and should be relatively fast but still 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
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)
回答3:
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.
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.
回答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')
来源:https://stackoverflow.com/questions/47254299/finding-the-longest-palindrome-within-a-string-in-python