一、啥是最长回文子串问题

咱先说说啥是回文串。回文串就是那种正着读和反着读都一样的字符串。比如说“level”“noon”,这些都是回文串。那最长回文子串问题呢,就是在一个给定的字符串里,找出最长的那个回文子串。

举个例子,有个字符串 “babad”,这里面最长的回文子串就是 “bab” 或者 “aba”。再比如 “cbbd”,最长回文子串就是 “bb”。这问题在很多实际场景里都能用到,像文本处理、生物信息学里分析 DNA 序列啥的。

二、传统方法解决最长回文子串问题

暴力枚举法

暴力枚举法就是把这个字符串里所有可能的子串都找出来,然后一个个判断是不是回文串,最后找出最长的那个。

下面是用 Python 实现的代码示例:

# 技术栈名称:Python
def longest_palindrome(s):
    n = len(s)
    max_length = 0
    longest_substring = ""
    # 遍历所有可能的子串起始位置
    for i in range(n):
        # 遍历所有可能的子串结束位置
        for j in range(i, n):
            sub_str = s[i:j + 1]
            # 判断子串是否是回文串
            if sub_str == sub_str[::-1]:
                # 如果是回文串且长度大于当前最长长度
                if len(sub_str) > max_length:
                    max_length = len(sub_str)
                    longest_substring = sub_str
    return longest_substring

s = "babad"
print(longest_palindrome(s))  # 输出最长回文子串

暴力枚举法的时间复杂度是 $O(n^3)$,因为有两层循环来找出所有子串,判断子串是否是回文串又需要 $O(n)$ 的时间。空间复杂度是 $O(1)$,只用到了常数级的额外空间。

中心扩展法

中心扩展法就是以每个字符或者每两个相邻字符为中心,向两边扩展,看看能形成多长的回文串。

Python 代码示例如下:

# 技术栈名称:Python
def longest_palindrome(s):
    if len(s) < 2:
        return s
    start, end = 0, 0
    for i in range(len(s)):
        # 以单个字符为中心扩展
        len1 = expand_around_center(s, i, i)
        # 以两个相邻字符为中心扩展
        len2 = expand_around_center(s, i, i + 1)
        max_len = max(len1, len2)
        if max_len > end - start:
            start = i - (max_len - 1) // 2
            end = i + max_len // 2
    return s[start:end + 1]

def expand_around_center(s, left, right):
    while left >= 0 and right < len(s) and s[left] == s[right]:
        left -= 1
        right += 1
    return right - left - 1

s = "babad"
print(longest_palindrome(s))  # 输出最长回文子串

中心扩展法的时间复杂度是 $O(n^2)$,因为要遍历每个字符,每个字符扩展的时间复杂度是 $O(n)$。空间复杂度是 $O(1)$。

三、Manacher 算法登场

算法核心思想

Manacher 算法的核心思想就是利用已经计算过的回文串信息,避免重复计算。它通过在字符串里插入特殊字符,把奇数长度和偶数长度的回文串统一处理,然后用一个数组来记录每个位置的回文半径。

具体步骤

  1. 插入特殊字符:在原字符串的每个字符前后都插入一个特殊字符,比如 “#”,这样能把奇数长度和偶数长度的回文串统一成奇数长度的回文串。例如,字符串 “abba” 会变成 “#a#b#b#a#”。
  2. 初始化变量:用一个数组 p 来记录每个位置的回文半径,还有两个变量 centerright 分别记录当前最右回文串的中心位置和右边界。
  3. 遍历字符串:从左到右遍历字符串,对于每个位置 i,根据 iright 的关系来计算 p[i]
    • 如果 iright 左边,就可以利用对称位置的信息来初始化 p[i]
    • 如果 iright 右边,就只能从 i 开始向两边扩展来计算 p[i]
  4. 更新 centerright:如果以 i 为中心的回文串右边界超过了 right,就更新 centerright

Python 代码实现

# 技术栈名称:Python
def longest_palindrome(s):
    # 插入特殊字符
    t = '#'.join('^{}$'.format(s))
    n = len(t)
    p = [0] * n
    c = r = 0
    for i in range(1, n - 1):
        i_mirror = 2 * c - i
        if r > i:
            p[i] = min(r - i, p[i_mirror])
        else:
            p[i] = 0
        # 尝试扩展以 i 为中心的回文串
        while t[i + 1 + p[i]] == t[i - 1 - p[i]]:
            p[i] += 1
        # 如果以 i 为中心的回文串右边界超过了 r
        if i + p[i] > r:
            c = i
            r = i + p[i]
    # 找出最大的回文半径
    max_len = 0
    center_index = 0
    for i in range(1, n - 1):
        if p[i] > max_len:
            max_len = p[i]
            center_index = i
    start = (center_index - max_len) // 2
    return s[start:start + max_len]

s = "babad"
print(longest_palindrome(s))  # 输出最长回文子串

四、Manacher 算法复杂度分析

时间复杂度

Manacher 算法的时间复杂度是 $O(n)$,因为每个字符最多被访问两次。在遍历字符串的时候,利用对称位置的信息避免了很多重复计算。

空间复杂度

空间复杂度是 $O(n)$,主要是用来存储 p 数组。

五、应用场景

文本处理

在文本处理里,有时候需要找出最长的回文子串,比如在文学作品里找一些特殊的回文段落,或者在代码里找一些对称的代码片段。

生物信息学

在生物信息学里,DNA 序列可以看成是一个字符串,找出最长回文子串可以帮助分析 DNA 序列的结构和功能。

六、技术优缺点

优点

  • 时间复杂度低:Manacher 算法的时间复杂度是 $O(n)$,比暴力枚举法和中心扩展法都快很多,在处理长字符串的时候优势明显。
  • 统一处理奇偶长度回文串:通过插入特殊字符,把奇数长度和偶数长度的回文串统一处理,简化了算法逻辑。

缺点

  • 空间复杂度较高:需要额外的 $O(n)$ 空间来存储 p 数组。
  • 理解难度较大:算法的核心思想和实现细节比较复杂,对于初学者来说不太容易理解。

七、注意事项

  1. 特殊字符选择:插入的特殊字符不能在原字符串里出现,不然会影响结果。
  2. 边界处理:在遍历字符串的时候,要注意边界条件,避免越界访问。

八、文章总结

Manacher 算法是一种非常高效的求解最长回文子串的算法,它利用已经计算过的回文串信息,避免了重复计算,把时间复杂度降到了 $O(n)$。虽然它的空间复杂度较高,理解难度也较大,但在处理长字符串的时候优势明显。在实际应用里,我们可以根据具体情况选择合适的算法来解决最长回文子串问题。