Python实现常见回文字符串算法详解

半杯拿铁
半杯拿铁
发布时间:2026-10-13 | 6人浏览过
原创

本文详解Python中常见的回文字符串处理算法,涵盖暴力破解、动态规划及Manacher算法。重点解析最长回文子串、前缀及子序列的求解思路,提供完整代码实现与复杂度分析,帮助开发者高效解决回文相关问题。

回文

利用Python内置的reversed()函数进行字符串翻转判断

def is_plalindrome(string):  return string == ''.join(list(reversed(string)))`

手动实现回文判断逻辑

def is_plalindrome(string):
  string = list(string)
  length = len(string)
  left = 0
  right = length - 1
  while left < right:
    if string[left] != string[right]:
      return False
    left += 1
    right -= 1
  return True

求解最长回文子串

暴力破解法

暴力破解法通过枚举所有可能的子串,并逐一判断其是否为回文。该方法的时间复杂度为O(n^3)。

动态规划法

def solution(s):
  s = list(s)
  l = len(s)
  dp = [[0] * l for i in range(l)]
  for i in range(l):
    dp[i][i] = True
    # 当 k = 2时要用到
    dp[i][i - 1] = True
  resLeft = 0
  resRight = 0
  # 枚举子串的长度
  for k in range(2, l+1):
    # 子串的起始位置
    for i in range(0, l-k+1):
      j = i + k - 1
      if s[i] == s[j] and dp[i + 1][j - 1]:
        dp[i][j] = True
        # 保存最长的回文起点和终点
        if resRight - resLeft + 1 < k:
          resLeft = i
          resRight = j
  return ''.join(s[resLeft:resRight+1])

动态规划法的时间复杂度为O(n^2),空间复杂度同样为O(n^2)。

Manacher算法

Manacher算法首先对原始字符串进行预处理,通过插入特定分隔符使所有回文串长度变为奇数。插入的符号需与原串字符不同,且不影响原串的回文性质。

例如,原始串aba处理后变为#a#b#a#;原始串abab处理后变为#a#b#a#b#。

定义回文半径为回文串最右端位置到对称轴的距离。Manacher算法构建回文半径数组RL,其中RL[i]表示以第i个字符为对称轴的回文半径。基于插入分隔符后的字符串,可计算出对应的RL数组。

char: # a # b # a #
RL:  1 2 1 4 1 2 1
RL-1: 0 1 0 3 0 1 0
i:   0 1 2 3 4 5 6
char: # a # b # a # b #
RL:  1 2 1 4 1 4 1 2 1
RL-1: 0 1 0 3 0 3 0 1 0
i:  0 1 2 3 4 5 6 7 8

计算RL[i] - 1,该值恰好等于原字符串中以对应位置为对称轴的最长回文长度。

核心难点在于如何高效求得RL数组,具体推导过程可参考相关技术文章。

以下是Manacher算法的具体Python实现代码。

def manacher(preS):
  s = '#' + '#'.join(preS) + '#'
  l = len(s)
  RL = [0] * l
  maxRight = pos = maxLen = 0
  for i in range(l):
    if i < maxRight:
      RL[i] = min(RL[2*pos - i], maxRight-i)
    else:
      RL[i] = 1
    while i - RL[i] >= 0 and i + RL[i] < l and s[i - RL[i]] == s[i + RL[i]]:
      RL[i] += 1
    if i + RL[i] - 1 > maxRight:
      maxRight = i + RL[i] - 1
      pos = i
  maxLen = max(RL)
  idx = RL.index(maxLen)
  sub = s[idx - maxLen + 1: idx + maxLen]
  return sub.replace('#', '')

空间复杂度:由于借助了一个辅助数组,空间复杂度为O(n)。

时间复杂度:尽管代码中存在内层循环,但该循环仅对尚未匹配的部分进行扩展。每个字符在算法执行过程中最多被访问一次,因此总时间复杂度为O(n)。

求解最长回文前缀

前缀定义为从字符串第一个字符开始的子串。

以下示例展示了最长回文前缀的求解结果。

abbabbc => abbc
abababb => ababa
sogou => s

将原字符串逆转后,问题转化为求原串前缀与逆串后缀相等且长度最大的值。这实际上是KMP算法中next数组求解问题的变体。

具体求解步骤:将原字符串逆转后拼接到原字符串末尾,中间使用'#'分隔,以避免内部字符串干扰匹配过程。

def longest_palindrome_prefix(s):
  if not s:
    return 0
  s = s + '#' + s[::-1] + '$'
  i = 0
  j = -1
  nt = [0] * len(s)
  nt[0] = -1
  while i < len(s) - 1:
    if j == -1 or s[i] == s[j]:
      i += 1
      j += 1
      nt[i] = j
    else:
      j = nt[j]
  return nt[len(s) - 1]

通过添加字符生成最短回文字符串

该问题与上述最长回文前缀问题的解决思路基本一致。

具体实例如下。

aacecaaa -> aaacecaaa # 添加 a
abcd -> dcbabcd # 添加 dcb

首先计算字符串的最长回文前缀,然后将剩余部分逆转并拼接到原字符串头部,即可得到问题所求的最短回文字符串。

def solution(s):
  length = longest_palindrome_prefix(s)
  return s[length:][::-1] + s

求解最长回文子序列

采用动态规划法求解。

  • 定义dp[i][j]表示子序列s[i..j]中存在的最长回文子序列长度。
  • 初始化边界条件:dp[i][i] = 1。
  • 当s[i] == s[j]为真时,状态转移方程为dp[i][j] = dp[i+1][j - 1] + 2。
  • 当s[i] == s[j]为假时,状态转移方程为dp[i][j] = max(dp[i+1][j], dp[i][j - 1])。
# 求得最长回文子序列的长度
def solution(s):
  l = len(s)
  dp = [[0] * l for i in range(l)]
  for i in range(l):
    dp[i][i] = 1
  # 枚举子串的长度
  for k in range(2, l+1):
    # 枚举子串的起始位置
    for i in range(0, l-k+1):
      j = i + k - 1
      if s[i] == s[j]:
        dp[i][j] = dp[i + 1][j - 1] + 2
      else:
        dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
  return dp[0][l-1]

该动态规划方法的时间复杂度为O(n^2),空间复杂度为O(n^2)。

总结

以上介绍了Python实现常见回文字符串算法的方法,希望能为读者提供帮助。如有任何疑问,欢迎留言交流,我们将及时回复。感谢大家对本站的支持!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表