LeetCode 5. 最长回文子串(中)
Posted tonydandelion2014
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 5. 最长回文子串(中)相关的知识,希望对你有一定的参考价值。
5. 最长回文子串(中)
📖 题目
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
📝 示例
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
输入: "cbbd"
输出: "bb"
🛰 考察知识点
字符串、数组、马拉车算法(Manacher’s Algorithm)、中心对称约束
💡 核心思想
1、先对字符串进行预处理,两个字符之间加上特殊符号#
2、然后遍历整个字符串,用一个数组来记录以该字符为中心的回文长度,为了方便计算右边界,我在数组中记录长度的一半(向下取整)
3、每一次遍历的时候,如果该字符在已知回文串最右边界的覆盖下,那么就计算其相对最右边界回文串中心对称的位置,得出已知回文串的长度
4、**判断该长度和右边界,如果达到了右边界,那么需要进行中心扩展探索。**当然,如果第3步该字符没有在最右边界的“羽翼”下,则直接进行中心扩展探索。进行中心扩展探索的时候,同时又更新右边界
5、最后得到最长回文之后,去掉其中的特殊符号即可
有一种情况没有说明,比如i > mx的情况(例如刚开始对字符串进行遍历时),这种情况也是直接用中心展开求得半径
首先要利用中心展开算法求以某个特定节点为中心的最长回文
def getExpandLength(left, right, s):
'''
中心展开求回文字符串长度
'''
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
💾 Python版本
一个不成功的版本 时间复杂度为 $ O(n^2) $,一但输入数据太长,就无法执行。
class Solution:
def longestPalindrome(self, s: str) -> str:
if s == "":
return ""
if len(s) == 1:
return s
left = 0
start_c = s[left]
max_palindromes = ""
max_palindromes_length = 0
sing_palindrome = True
while left < len(s):
for i in range(left+1, len(s)):
if s[i] == start_c:
is_palindrome = True
sub_str = s[left:i+1]
sub_str_len = len(sub_str)
# 检车是否为回文字符
if len(sub_str)%2==0: # 偶数
mid = int(sub_str_len/2)
front = sub_str[:mid]
back = sub_str[mid:]
else: # 奇数
mid = int(sub_str_len/2)
front = sub_str[:mid]
back = sub_str[mid+1:]
mid_length = len(back)
# for j in range(len(back)-1, 0, -1):
for j,v in enumerate(front):
if v != back[mid_length-1-j]:
is_palindrome = False
if is_palindrome:
sing_palindrome = False
if sub_str_len > max_palindromes_length:
max_palindromes_length = sub_str_len
max_palindromes = sub_str
left += 1
if left < len(s):
start_c = s[left]
if sing_palindrome:
return s[0]
else:
return max_palindromes
一个成功的Python版本
class Solution:
def getExpandLength(self, left, right, s):
'''
中心展开求回文字符串长度
'''
# 假设输入的是left=4 right=6 s='babcbabcba' => 以i=5为中心向两边搜寻
# 0 1 2 3 4 5 6 7 8 9
# b a b c b a b c b a
# 则 left = 3/2/1 和 right = 7/8/9 可构成回文 a b c b a b c b a
# 此时当left再减少时 left=0 满足条件 right=10=len(s) 就不满足条件了 while循环结束 然后返回回文直径为 rigth - left - 1(i=5的中心点不计算在内) = 10-0-1 = 9 则半径为9//2=4向下取整
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
def longestPalindrome(self, s: str) -> str:
if len(s) < 2: return s
s = '#' + '#'.join(list(s)) + '#'
p = [0 for x in range(len(s))]
max_id = -1
max_radius = -1
c = -1
mx = -1
for i in range(len(s)):
if i > mx:
radius = self.getExpandLength(i, i, s) // 2
p[i] = radius
c = i
mx = i + radius # mx = c + p[c] / min = c - p[c]
if radius > max_radius:
max_id, max_radius = i, radius
else:
j = 2*c - i
if j - p[j] > 2 * c - mx: # 在C的radius范围内
p[i] = p[j]
elif j - p[j] < 2 * c - mx: # 越界了
p[i] = mx - i
else: # 如果不是上述两种情况 就说明j-p[j]==min,依赖之前的有效信息,以及无法为当前以i为回文中心,寻找最长回文字符半径提供依据,所以要调用getExpandLength方法,用中心拓展的方法手动扩展回文半径
radius = self.getExpandLength(i-p[j]-1, i+p[j]+1, s) // 2
p[i] = radius
if i + radius > mx: # 如果以当前i所在位置为回文中心,其右边大于原来以c所在位置为回文中心的右边,则更新其回文中心为c,mx修正为新的右边 mx = i + radius
c = i
mx = i + radius
if radius > max_radius: # 如果新的回文半径大于原max_radius 就更新新的radius
max_id, max_radius = i, radius
return s[max_id-max_radius:max_id+max_radius+1].replace('#', '')
print("leet code accept!!!")
s = ["babad", "cabadabae", "ab", "aa", "abc", "cbbd", "babad"]
Answer = ["bab", "abadaba", "a", "aa", "a", "bb", "bab"]
if __name__ == "__main__":
solution = Solution()
for i in range(len(s)):
print("-"*50)
result = solution.longestPalindrome(s[i])
# print(result)
# print(Answer[i])
print(result==Answer[i])
- 一个更加高效的版本 推荐这个版本 优雅且兼具易读性 在理解了上面那个Manacher’s Algorithm算法的基础上,理解这个会更加容易。
class Solution:
def longestPalindrome(self, s) -> str:
if len(s) < 2:
return s
# 先插入"#" 开头和结尾都要插入
s = '#' + '#'.join(list(s)) + '#'
# 申明radius list
p = [0 for x in range(len(s))]
# 申明剩余变量
max_str = ""
c = 0
mx = 0
for i in range(len(s)):
if i < mx: # i是在以某一z值为中心的最长回文子串的半径之内
j = c * 2 - i # j为i以c为原点的对称点 / i关于center的对称点
p[i] = min(p[j], mx-i) # 这行代码类似上面那个版本的下述
"""
if j - p[j] > 2 * c - mx:
p[i] = p[j]
elif j - p[j] < 2 * c - mx:
p[i] = mx - i
"""
# 尝试继续向两边扩展,更新p[i],如果不行就停止,问题也不大 注意添加判断条件 根据Python的判断条件 不满足下标条件的就不进行判等 所以判等条件放到最后
while (i-p[i]-1) >= 0 and (i+p[i]+1) < len(s) and s[i-p[i]-1] == s[i+p[i]+1]:
p[i] += 1
if p[i] + i > mx: # 向右边推动 更新中心
c = i
mx = p[i] + i
if 1 + 2*p[i] > len(max_str): # 更新最长串
max_str = s[i-p[i]:i+p[i]+1]
return max_str.replace("#", "")
💻 有效语法糖
1、声明长度为N的、每个元素都是0的数组
_list = [0 for x in range(len(s))]
2、在字符串的中间添加“#”
def preprocess(self, s: str) -> str:
s_copy = "#"
for i in s:
s_copy = s_copy + i + "#"
return s_copy
或者 Python join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
s = '#' + '#'.join(list(s)) + '#'
s1 = "-"
s2 = ""
seq = ("r", "u", "n", "o", "o", "b") # 字符串序列
print (s1.join( seq ))
print (s2.join( seq )
r-u-n-o-o-b
runoob
3、Python3的向下取整
>>> 13//2
6
4、此题目中,可能出现变量为0的情况,因此在初始化时,将各个变量初始化为了-1。需注意。
max_id = -1
max_radius = -1
c = -1
mx = -1
5、python3字符串替换
'a#b#c'.replace('#', '')
以上是关于LeetCode 5. 最长回文子串(中)的主要内容,如果未能解决你的问题,请参考以下文章