算法学习笔记——动态规划:最长回文子串

本文介绍了如何利用动态规划和马拉车算法来寻找字符串中的最长回文子串。动态规划方法的时间复杂度为O(n^2),而马拉车算法可以将复杂度降低到O(n)。马拉车算法通过预处理和回文串的对称性质进行优化,提高了求解效率。文章详细解释了两种方法的实现步骤,并提供了相应的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最长回文子串

给出字符串s,求s的最长的回文子串

法一

从左向右遍历,将每个字符都可以尝试作为中心点,从中心向左右两边扩散回文串,如果左右相等则继续扩散

会出现两种情况:可能是类似 aba 的字符串,也可能是类似 abba 的情况
在这里插入图片描述

复杂度O(n^2)

法二 动态规划

用dp[i][j]表示从下标i到j的子串是否为回文串
初始化:dp[i][i]为True

  • 用j从左往右遍历每个字符s[j]
  • 对于某个j,用i从右往左遍历其左侧的每个字符s[i](保证i始终小于j)
  • 状态转移方程:
    i==j,dp[i][i]=True
    j-i==1,dp[i][j]=(s[i]==s[j])
    j-i>1, dp[i][j]=(s[i]==s[j]) and dp[i+1][j-1]
  • 若需要输出这个最长回文子串,则每次更新dp[i][j]时,若刷新了最长的记录,同步更新当前最长的回文子串长度maxLen=j-i+1和对应的起点i、终点j
  • 复杂度O(n^2)

法三 马拉车算法

复杂度O(n),源于两个核心改进:

  1. 在每个字符之间都插入一个"#",这样长度为奇数和偶数的回文都同义处理,不用分类
  2. 利用历史信息,避免重复工作
    具体而言,从左到右处理每个字符s[i],维护数组p[i]代表以s[i]为中心的最长回文串的半径
    假定当前已经知道左侧一部分p[i](对应不同回文串),那么其中最有用的那个回文串是[右端点最大的那个回文串],我们记这个最有用的回文串的中心点s[c](为什么要维护这个点?因为这个回文串右端点最远,给出了最多的对称信息,我们可以利用其更新当前靠右的最新p[i]!),那么我们可以利用s[c]左半边的p[i']信息推知s[c]右半边的未知p[i]

例如当前最长回文串的中心在c=11,由于其左右两边对称,那么右边的p[13]=左边的p[9]=1;
在这里插入图片描述
但是这种对称性的利用有限制条件:仅在p[c]指定的最大回文范围内(即图中的LR之间)对称,超出这个范围后,我们仍然需要手动判断右侧的p[i]
例如对于p[7]p[15],我们只能保证两条绿色实线划定的范围必定是对称的(至少有p[15]>=R-i=5),然而p[i']=7,回文范围超过了左边界L,因此不一定有p[i]=p[i']=7,我们对于i=15检查字符s[21]='c'果然不等于s[9]='b',也就是说只有p[15]=5而非p[15]=p[7]=7
在这里插入图片描述
上述过程的核心逻辑为:
if P[ i’ ] < R – i,(i关于c的左侧对称点i'的回文长度没有超出绿色范围)
then P[ i ] ← P[ i’ ]
else P[ i ] ≥ R - i. (此时要穿过R逐个字符判定p[i]最大究竟是多少)

图源:【算法】——Manacher Algorithm(马拉车算法)

代码:

def manacher(A: str):
    s = '#' + '#'.join(list(A)) + '#'

    cIdx = 0# 当前的最有用的回文子串的中心点
    R = -1# 目前已知的最长回文子串的右边界
    p =[0 for i in range(len(s))]

    ans_cIdx=0# 最长回文串的中心点
    max_length = 0# 最长回文串长度
    for i in range(1,len(s)-1):
        if i < R:
            p[i] = min(R-i,p[2*cIdx-i])# i关于c的左对称点为2*cIdx-i
        else:
            p[i] = 1# 此处是右端新的单个字符
        
        #两边扩散,寻求更长的回文串
        while i-p[i]>=0 and i+p[i]<len(p) and s[i-p[i]] == s[i+p[i]]:
            p[i] += 1
        
        if R < i + p[i]:
            R = i + p[i]
            cIdx = i
        if p[i]-1 > max_length:#当前最长的回文子串
            max_length = p[i]-1
            ans_cIdx = i
    tmp = s[ans_cIdx-p[ans_cIdx]+1:ans_cIdx+p[ans_cIdx]]
    ans = "".join(tmp.split("#"))

    print(max_length)
    print(ans)
    return max_length
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值