这两天被这个题弄得是有些崩溃了,因为之前试的两种方法都是超时了,所以弄得后面都有些不想弄了。还好有度娘,最后用的是从中间往两边扩展的方法得此解决,真的是如释重负啊!废话不说,讲正文贴代码。
题目如下:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba"也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
方法一:暴力法
有些人会忍不住提出一个快速的解决方案,不幸的是,这个解决方案有缺陷(但是可以很容易地纠正):
反转 SS,使之变成 S'S′。找到 SS 和 S'S′ 之间最长的公共子串,这也必然是最长的回文子串。
这似乎是可行的,让我们看看下面的一些例子。
例如,S = {“caba”}S=“caba” , S' = {“abac”}S′=“abac”:
SS 以及 S'S′ 之间的最长公共子串为 {“aba”}“aba”,恰恰是答案。
让我们尝试一下这个例子:S ={“abacdfgdcaba”}S=“abacdfgdcaba” , S' = {“abacdgfdcaba”}S′=“abacdgfdcaba”:
SS 以及 S'S′ 之间的最长公共子串为{“abacd”}“abacd”,显然,这不是回文。
这种方法确实最好理解的额,但效率确实是低。所以放在编辑器里面跑,都是直接报超出时间限制的错误的,这是最dan疼的
def longestPalindrome(self, s):
s_inverse = s[::-1]
max = 0
maxStr = ""
if len(s) < 2:
return s
for start in range(len(s)):
for end in range(start+1, len(s)+1):
if s.count(s_inverse[start:end]) > 0:
index = s.index(s_inverse[start:end])
start_inverse = len(s) - end
if (end - start > max) and (index == start_inverse):
max = end - start
maxStr = s_inverse[start:end]
return maxStr
这种方法是巧妙地运用了python 字符串的一些内置函数执行的,代码量很少,但跑起来时间不短
大概就是报这样的提醒,就是说你的程序没问题,但是太low了,到后面它都不愿意测试了
def longestPalindrome(self, s):
max = 0
maxStr = ""
if len(s) < 2:
return s
for start in range(len(s)):
for end in range(len(s)-1, start-1, -1):
start_copy = start
end_copy = end
while s[start_copy] == s[end_copy] and start_copy < end and end_copy >
start:
start_copy += 1
end_copy -= 1
if start_copy == end and end_copy == start:
if end - start >= max:
max = end - start
maxStr = s[start: end+1]
return maxStr
这个方法是借鉴了动态规划的思想,但是也是效率过低,报如上的错误,贴出来可供参考
方法二:动态规划
为了改进暴力法,我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑{“ababa”}“ababa” 这个示例。如果我们已经知道 {“bab”}“bab” 是回文,那么很明显,{“ababa”}“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
我们给出 P(i,j)P(i,j) 的定义如下:
P(i,j) = \begin{cases} \text{true,} &\quad\text{如果子串} S_i \dots S_j \text{是回文子串}\\ \text{false,} &\quad\text{其它情况} \end{cases}P(i,j)={true,false,如果子串Si…Sj是回文子串其它情况
因此,
P(i, j) = ( P(i+1, j-1) \text{ and } S_i == S_j )P(i,j)=(P(i+1,j−1) and Si==Sj)
基本示例如下:
P(i, i) = trueP(i,i)=true
P(i, i+1) = ( S_i == S_{i+1} )P(i,i+1)=(Si==Si+1)
这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…
复杂度分析
-
时间复杂度:O(n^2)O(n2), 这里给出我们的运行时间复杂度为 O(n^2)O(n2) 。
-
空间复杂度:O(n^2)O(n2), 该方法使用 O(n^2)O(n2) 的空间来存储表。
代码如下:
def longestPalindrome(self, s):
dpArray = np.zeros([1000, 1000])
maxIndex = -1
maxStr = ""
if len(s) < 2:
return s
dpArray[0][0] = 1
for end in range(1, len(s)):
dpArray[end][end] = 1
for start in range(end+1):
if end == start+1 and s[end] == s[start]:
dpArray[start][end] = 1
if end > start + 1:
dpArray[start][end] = dpArray[start+1][end-1] and (s[end] == s[start])
if end - start >= maxIndex and dpArray[start][end]:
maxIndex = end - start
maxStr = s[start:end+1]
print(maxStr)
print(dpArray[0:len(s), 0:len(s)])
return maxStr
看起来确实是牛逼了一些,但不知为何在我的电脑上跑,它还是说超出时间限制,当时真的是快把我给气炸了
方法三:中心扩展算法
事实上,只需使用恒定的空间,我们就可以在 O(n^2)O(n2) 的时间内解决这个问题。
我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 1个这样的中心。
你可能会问,为什么会是 2n - 1个,而不是 n 个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如{“abba”}“abba” 的中心在两个 {‘b’}‘b’ 之间)。
代码如下:
def longestPalindrome(self, s):
maxLen = 0
maxStr1 = ""
for start_current in range(len(s)):
if len(s) - start_current <= int(maxLen/2):
break
start_head = start_current
start_last = start_current + 1
while start_head >= 0 and start_last < len(s) and s[start_head] == s[start_last]:
start_head -= 1
start_last += 1
if start_last - start_head - 1 >= maxLen:
maxLen = start_last - start_head - 1
maxStr1 = s[start_head + 1: start_last]
start_head = start_current
start_last = start_current
while start_last < len(s) and start_head >= 0 and s[start_head] == s[start_last]:
start_head -= 1
start_last += 1
if start_last - start_head - 1 >= maxLen:
maxLen = start_last - start_head - 1
maxStr1 = s[start_head + 1: start_last]
return maxStr1
到了这种方法总算是给跑出来了,而且它的速度也算是中等吧!真的是一把鼻涕一把泪啊,本来想再把速度给提高的,但是被之前的失败经历给打击了,容我缓一段时间再来刚,最后把它的执行时间给贴出来