最长回文子串
最长回文子串
获取最长回文子串
【题目】
给定一个字符串str,返回str中最长回文子串的长度。
【举例】
str=“123”,其中的最长回文子串为"1"、“2"或者"3”,所以返回1。
str=“abc1234321ab”,其中的最长回文子串为"1234321",所以返回7。
暴力遍历最长回文字符串
以每个字符为中心,往外扩,看左右两边字符是否相同;
最坏情况每次扩至字符串两端,因此最坏时间复杂度为。
注意:
对称有实轴对称和虚轴对称之分。
实轴以字符为中心,e.g. “bab”;
虚轴以间隔为中心,e.g. “baab”;
相应代码
# 暴力遍历最长回文字符串
def solution(s):
max_len = 0
# 实轴对称
for i in range(len(s)):
count = 1
j = 1
while i - j >= 0 and i + j < len(s) and s[i - j] == s[i + j]:
count += 2
j += 1
if count > max_len:
max_len = count
# 虚轴对称
for i in range(len(s)):
count = 0
j = 0
while i - j >= 0 and i + 1 + j < len(s) and s[i - j] == s[i + 1 + j]:
count += 2
j += 1
if count > max_len:
max_len = count
return max_len
# 简单测试
if __name__ == '__main__':
s1 = "123"
s2 = "abc1234321ab"
print(solution(s1)) # 1
print(solution(s2)) # 7
Manacher算法
即马拉车算法
构建辅助字符串,解决实轴对称和虚轴对称分情况处理情形,上文的暴力遍历方法也可以使用,Morris遍历也用了类似的想法。e.g. “abc1234321ab” ——> “#a#b#c#1#2#3#4#3#2#1#a#b#”
查看扩充最右位置
若i不在最右位置或者i’(i的回文对称)回文左侧超过左侧位置,则需扩充最右位置;
反之其余直接获取最大回文半径;
而最右位置由0到2N-1,单调递增
因此时间复杂度为。
优化之处在于当i包含在最右位置内,能利用前面i’的回文对称半径,加速匹配。
原字符串长度为N,最长回文半径为r,
若r为偶数,则最长回文字符串长度为2r(r+r);
若r为奇数,则最长回文字符串长度为2r-1(r+r-1);
辅助字符串长度为2N+1,最长回文半径为R,最长回文字符串为L+1;
若r为偶数,则辅助字符串最长回文半径为R=2r+1(r+r+1);
若r为奇数,则辅助字符串最长回文半径为2r-1(r+r-1);
因此最长回文字符串长度为辅助字符串最长回文半径-1。
相应代码
# 马拉车算法
# 插入辅助字符'#',构建辅助字符串
def get_manacher_str(s):
char_arr = [0 for i in range(2 * len(s) + 1)]
for i in range(len(char_arr)):
if i % 2 == 0:
char_arr[i] = '#'
else:
char_arr[i] = s[i // 2]
return ''.join(char_arr)
# 获取最长回文子串的长度
def get_long_pal_sub_str_len(s):
manacher_str = get_manacher_str(s)
pal_arr = [0 for i in range(len(manacher_str))]
index = -1
p_r = -1
max_len = -1
for i in range(len(manacher_str)):
if i < p_r:
pal_arr[i] = p_r - i
if 2 * index - i >= 0 and pal_arr[i] < pal_arr[2 * index - i]:
pal_arr[i] = pal_arr[2 * index - i]
else:
pal_arr[i] = 1
j = 1
while i - j > 0 and i + j < len(manacher_str) and manacher_str[i - j] == manacher_str[i + j]:
pal_arr[i] += 1
j += 1
if i + j > p_r:
index = i
p_r = i + j
if pal_arr[i] > max_len:
max_len = pal_arr[i]
return max_len - 1
# 简单测试
if __name__ == '__main__':
s1 = "123"
s2 = "abc1234321ab"
print(get_manacher_str(s1)) # #1#2#3#
print(get_manacher_str(s2)) # #a#b#c#1#2#3#4#3#2#1#a#b#
print(get_long_pal_sub_str_len(s1)) # 1
print(get_long_pal_sub_str_len(s2)) # 7
末尾添加最短字符串构成回文串
算法思路
包含末尾字符的最大回文字符串。
算法主体原理仍用Manacher算法
扩充右边界达到末尾时,即获取最大回文半径contains_end
。
最长回文字符串长度为辅助字符串最长回文半径-1contains_end-1
,原字符串长度减去最长回文字符串长度即为要添加的最短字符串长度len-(contains_end - 1)
左侧的非回文部分字符串对称到右侧,即为需要再末尾添加最短字符串。
相应代码
# 末尾添加构成回文的最短字符串
def get_short_added_pal_str(s):
manacher_str = get_manacher_str(s)
pal_arr = [0 for i in range(len(manacher_str))]
index = -1
p_r = -1
max_len = -1
res = 0
for i in range(len(manacher_str)):
if i < p_r:
pal_arr[i] = p_r - i
if 2 * index - i >= 0 and pal_arr[i] < pal_arr[2 * index - i]:
pal_arr[i] = pal_arr[2 * index - i]
else:
pal_arr[i] = 1
j = 1
while i - j > 0 and i + j < len(manacher_str) and manacher_str[i - j] == manacher_str[i + j]:
pal_arr[i] += 1
j += 1
if i + j > p_r:
index = i
p_r = i + j
if pal_arr[i] > max_len:
max_len = pal_arr[i]
# 以下为新增代码
if i + j == len(manacher_str):
contains_end = j
break
arr = [0 for i in range(len(s) - (contains_end - 1))]
for i in range(len(arr)):
arr[i] = s[len(arr) - 1 - i]
return ''.join(arr)
# 简单测试
if __name__ == '__main__':
s1 = "123"
s2 = "abc1234321ab"
print(get_short_added_pal_str(s1)) # 21
print(get_short_added_pal_str(s2)) # a1234321cba
s3 = "abcd123321"
print(get_short_added_pal_str(s3)) # dcba
这次的代码写的真的没有左神优雅!
日后回顾再改写
有任何疑问和建议,欢迎在评论区留言和指正!
感谢您所花费的时间与精力!
下一篇: 叔叔干她去