1.暴力枚举法O(n^3)
暴力枚举法寻找最长回文子串是通过检查字符串中的所有可能子串,并验证它们是否是回文来实现的。这种方法的时间复杂度很高,为 O(n^3),因为需要枚举所有子串并对每个子串进行回文验证。
暴力枚举法实现步骤
- 枚举所有子串:使用两层循环来生成字符串的所有可能子串。
- 检查每个子串是否为回文:对于每个子串,使用一个辅助函数来验证它是否是回文。
- 记录最长回文子串:如果找到的回文子串比已记录的更长,更新最长回文子串的长度。
def is_palindrome(s):
return s == s[::-1]
def longest_palindrome_brute(s):
max_len = 0
n = len(s)
for start in range(n):
for end in range(start + 1, n + 1):
substring = s[start:end]
if is_palindrome(substring) and len(substring) > max_len:
max_len = len(substring)
return max_len
# 示例
input_string = "babad"
print(longest_palindrome_brute(input_string)) # 输出最长回文子串的长度
- is_palindrome 函数:这个辅助函数接收一个字符串,通过简单的字符串反转检查它是否是回文。
- longest_palindrome_brute 函数:
max_len
用于跟踪找到的最长回文子串的长度。- 外层循环
start
从 0 开始,到字符串长度n
。 - 内层循环
end
从start + 1
开始,直到n + 1
,这样可以包含所有可能的子串。因为range是左开右闭的,range(n+1),生成的数列只能到n,正好符合要求 - 对于每个子串,使用
is_palindrome
函数检查是否为回文,并且如果是,并且长度大于max_len
,则更新max_len
。
性能和限制
虽然这种暴力枚举法简单直观,但由于其高时间复杂度,它在处理较长字符串时会非常慢。通常,对于实际应用,更推荐使用中心扩展或动态规划等更高效的算法。暴力枚举法通常用于教学目的或在数据量非常小的情况下使用。
2. 中心扩展法O(n^2)
使用中心扩展法来寻找字符串中的最长回文子串是一种效率更高的方法,相比于暴力枚举法,它的时间复杂度通常是 O(n^2),并且空间复杂度很低。下面提供一个Python代码实现,用于寻找并返回字符串中的最长回文子串的长度:
def longest_palindrome(s):
if not s:
return 0
def expand_around_center(left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
# 注意,当循环结束时,left 和 right 指向的字符不属于回文,
# 正确的回文长度是 right - left - 1
return right - left - 1
max_len = 0
for i in range(len(s)):
# 以 s[i] 为中心的奇数长度回文
len1 = expand_around_center(i, i)
# 以 s[i] 和 s[i+1] 之间为中心的偶数长度回文
len2 = expand_around_center(i, i + 1)
# 更新找到的最长回文长度
max_len = max(max_len, len1, len2)
return max_len
# 示例
input_string = "babad"
print(longest_palindrome(input_string)) # 输出最长回文子串的长度
left -= 1 right += 1 return right - left - 1
问:为什么是left和right分别-1和+1,而返回的是-1而不是-2
答:比如xabcbaq,abcba是回文,
-1情况:两个a的索引分别是1和5,下一次循环的索引指向的x和q,索引是0和6,right-left=6,但实际回文长度是5,所以要right-left-1,
+1情况:如果在索引a的时候就停止,5-1=4,回文长度应该是5,所以是right-left+1。但我们的判断条件是越界了之后,所以就要-1
2出现的情况:两次差值确实是2
3.动态规划O(n^2)
动态规划是解决回文子串问题的一种非常有效的方法,特别是在需要频繁检索或涉及到更复杂结构的情况下。动态规划的核心思想是将大问题分解成小问题,并存储已解决问题的结果以避免重复计算。对于找到最长的回文子串,动态规划方法考虑所有可能的子串,并通过建立一个表来判断每个子串是否为回文。
动态规划算法概述:
-
初始化:创建一个二维布尔数组
dp[i][j]
,其中dp[i][j]
表示从索引i
到索引j
的子串是否是回文。初始化所有单字符子串dp[i][i]
为True
,因为单个字符总是回文。 -
填充表格:对于长度大于1的子串,检查和更新
dp
表。如果s[i]
等于s[j]
且子串s[i+1...j-1]
是回文(即dp[i+1][j-1]
是True
),则s[i...j]
也是回文。 -
更新最长回文长度和起始点:每次发现一个回文子串时,检查其长度是否是当前已知的最长回文长度,如果是,则更新最长回文长度和起始索引。
def longest_palindrome_dp(s):
n = len(s)
if n < 2:
return s # 如果字符串长度小于2,直接返回原字符串
dp = [[False] * n for _ in range(n)]
start, max_len = 0, 1
# 所有单个字符的子串都是回文
for i in range(n):
dp[i][i] = True
# 构建DP表并找到最长回文子串
for length in range(2, n + 1): # 子串长度
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
if length == 2 or dp[i + 1][j - 1]:
dp[i][j] = True
if length > max_len:
max_len = length
start = i
return s[start:start + max_len]
# 示例
input_string = "babad"
print(longest_palindrome_dp(input_string)) # 输出最长回文子串
4.Manacher算法O(n)
Manacher算法是一种高效的算法,用于在O(n)时间内找到一个字符串中最长的回文子串。这个算法由Albert Manacher于1975年提出,显著优于暴力法(O(n^3))和动态规划方法的时间复杂度(O(n^2)),因此在需要处理长字符串时非常有用。
Manacher算法的核心思想
Manacher算法的核心思想是利用回文的对称性和已经找到的信息来避免重复计算,从而实现线性时间复杂度。具体来说,它将原始字符串转换为一个新的字符串,这个新字符串在每个字符之间插入一个特殊字符(例如#
),并在字符串的开始和结束插入两个特殊字符(例如^
和$
)。这个处理确保了所有的回文都是奇数长度,从而简化了算法的实现。
主要步骤
-
预处理:将原始字符串转化为一个新字符串,在每个字符之间插入
#
,并在开始和结束插入^
和$
。例如,字符串"abcba"
变成^#a#b#c#b#a#$
。 -
初始化:
p[i]
表示以t[i]
为中心的回文的半径长度(包括中心字符)。center
和right
分别记录当前回文的中心和右边界。
-
遍历新字符串:
- 对每个字符
t[i]
,尝试扩展回文。 - 使用已知的回文信息来减少计算工作。如果
i
在已知的回文右边界right
内部,则可以利用p[2*center - i]
这个信息来推测当前i
处的回文长度。 - 尝试扩展回文,更新
p[i]
。 - 如果扩展的回文超过了右边界,更新
center
和right
。
- 对每个字符
-
提取结果:
p[i]
数组中的最大值即为最长回文的长度(注意结果需要调整为原始字符串的长度)。
def manacher(s):
# 转换字符串,每个字符间添加特殊字符
t = '#'.join(f"^{s}$")
n = len(t)
p = [0] * n
center = right = 0
max_len = 0
for i in range(1, n-1):
p[i] = (right > i) and min(right - i, p[2*center - i])
# 尝试扩展回文,即使之前已经部分计算过
while t[i + p[i] + 1] == t[i - p[i] - 1]:
p[i] += 1
# 如果扩展的回文超过了右边界,更新中心和右边界
if i + p[i] > right:
center, right = i, i + p[i]
# 更新最长回文长度
max_len = max(max_len, p[i])
return max_len
input_string = "babad"
print(manacher(input_string)) # 输出: 3