Manacher算法——求最长回文子串

Manacher算法是一种高效求解最长回文子串的方法。通过在原字符串中插入特殊字符,将所有回文串转换为奇数长度,从而简化问题。利用回文特性减少不必要的比较,达到O(n)的时间复杂度。

    Manacher算法是用来求解一个给定字符串的最长回文子串,回文就是将字符串翻转后,与原来一样。这个算法通过添加字符,巧妙的将回文长度为偶数的情况转化成奇数,简化了问题的步骤,并且利用回文的性质,将去除了冗余的比较,从而将复查度降到O(len),能够求出以每个字符为中心的最长回文,从而求出最长的回文子串。

现在,简述一下算法的过程:

  1. 在相邻的两个字符之间(包括首尾)添加特殊字符(也就是原串不会出现的同一个字符,比如'#'),通过这个简单的步骤,所有的回文都变成奇数长度了,这个可以简单证明下,假设回文串长度是偶数 n,那么在它们之间加入特殊字符的个数为n-1 + 2,n-1个间隙,还有首尾,所以这时长度为n+n+1=2*n+1为偶数,同理,奇数长度添加后为2*n+1还是奇数。也就是我们只需要求出长度为奇数的回文,而不用考虑偶数长度了。
  2. 假设,以字符s[ i ]为中心的最长回文为 s[ i - len[ i ] , i + len[ i ] ],也就是说len[ i ]为这个回文的半径-1,由上面添加特殊字符后回文长度为2*n+1可知,2*len[ i ]+1=2*n+1,即len[ i ]就是以 i 为中心,去掉特殊字符后的回文串的长度,也就是我们要的答案。
  3. 现在是关键部分,假设,我们已知len[ j ] (1<=j<i)的值,并且知道rightmost=max{ j+len[ j ] },还有与它对应的下标 k,现在有下面几种情况:
  • rightmost<i,这种情况下,说明 i 附近的字符没有比较过,也没有已知的情况,所以len[ i ]=0,从0开始往两边比较
  • rightmost>=i,这种情况说明之前已经比较过i 附近的字符了,看下图:

我们已知k和 i ,还有k对应的len[ k ],也就知道s[ k-len[k], k+len[k] ]是个回文,现在找到i关于k的对称点2*k-i,如果2*k-i-len[ 2*k-i ]>=k-len[k],也就是左边那个橙色的回文串没有超过k这个回文串的范围,我们立马知道,len[ i ]>=len[ 2*k-i],也就是两个橙色的串是完全相同的,因为右边这个串是左边的翻转,而橙色部分又是回文串,翻转后完全相同,所以利用回文的性质,就省去了很多比较,我们只需要从len[ i ]=len[ 2*k-i]开始比较

但是如果2*k-i-len[ 2*k-i ]<k-len[k],也就是左边的回文串超出k这个回文串的范围,如下图:

这时i+len[2*k-i] 明显超过k+len[ k ],而超过的部分是未知的,所以要重新比较,也就是从len[ rightmost-i ] 开始比较
所以,通过两种情况可以发现,必需从len[ i ]=min( len[2*k-i] ,  rightmost-i )开始比较,直到出现左右两边不同为止,这时就是len[ i ]的值

复杂度证明:
我们可以发现,当len[2*k-i]<rightmost-i,也就是第一张图的情况,这时由于左右两边的橙色回文完全相同,并且绿色部分是翻转的,所以,len[ i ]不可能再增加,不需要匹配,复杂度为O(1),当len[2*k-i]>=rightmost-i时,len[ i ]=rightmost-i,也就是从rightmost开始比较,由于rightmost再不断递增,最多递增length(字符串S长度)次,平摊下来,每次比较的复杂度为O(1),所以总时间复杂度为length*O(1),即O(length),也就是O(n)的算法

整个算法就这么简单,模版如下:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

#define N 222222
char s[N],str[N];
int ans[N];
int Manacher(char src[], char tmp[], int len[], char unique, char begin)
{
    int i,j,k,rightmost,n=strlen(src),ret=0;
    tmp[0]=begin;
    tmp[1]=unique;
    for(i=0;i<n;++i)
    {
        tmp[i*2+2]=src[i];
        tmp[i*2+3]=unique;
    }
    n=n*2+2;
    tmp[n]='\0';
    for(i=0;i<n;++i)len[i]=0;
    rightmost=k=0;
    for(i=1;i<n;++i)
    {
        if(rightmost>=i)len[i]=min(len[2*k-i],rightmost-i);
        else len[i]=0;
        for(j=len[i]+1;tmp[i+j]==tmp[i-j];++j)++len[i];
        if(i+len[i]>rightmost)
        {
            rightmost=i+len[i];
            k=i;
        }
        ret=max(ret,len[i]);
    }
    return ret;
}
int main()
{
    while(~scanf("%s",str))
        printf("%d\n",Manacher(str,s,ans,'#','$'));
    return 0;
}

对应题目:
这题就是裸的模版题


<think>嗯,用户想找关于C语言实现最长回文子串算法或示例代码。我需要先回忆一下相关算法最长回文子串常用的方法有动态规划和Manacher算法。动态规划可能更适合用户,因为实现起来相对直观,而Manacher虽然高效但复杂一些。 首先,动态规划的思路是创建一个二维数组dp,其中dp[i][j]表示字符串从i到j是否为回文。初始状态是单个字符肯定是回文,即dp[i][i] = true。然后检查长度为2的子串,如果两个字符相同,也是回文。接着,对于长度大于2的情况,如果两端字符相同且中间子串是回文,则整个子串是回文。这种方法的时间复杂度是O(n²),空间复杂度也是O(n²)。 用户提到他们看到的引用里有动态规划的内容,比如引用[2]和[4]。所以应该优先考虑动态规划的实现。然后,我需要用C语言写出这个算法的代码示例。要注意正确管理内存和数组索引,避免越界错误。 另外,用户可能对Manacher算法也有兴趣,因为引用[3]提到了。Manacher算法的时间复杂度是O(n),但实现起来比较复杂,需要处理字符串的预处理,比如插入特殊字符来统一奇偶长度的回文。然后利用对称性来优化中心的扩展。不过对于初学者来说,动态规划可能更易理解。 在代码示例部分,动态规划的实现需要双重循环,外层循环是子串长度,内层是起始位置。同时记录最长回文子串的起始位置和长度。最后用strncpy提取子串。 可能需要检查示例代码是否有错误,比如数组的初始化是否正确,循环边界是否处理得当。比如,当所有字符都相同的情况,或者输入为空的情况。还要注意字符串的结束符处理。 然后,用户可能需要相关的问题,比如优化方法、不同算法的比较,或者空间复杂度的改进。动态规划的空间优化可以使用一维数组,但可能不太直观。而Manacher算法的优势在于线性时间,但代码复杂。 总之,用户的需是C语言实现示例,所以重点放在动态规划和Manacher的代码展示上,同时解释其原理,并附上相关问题帮助用户深入理解。</think>### C语言实现最长回文子串算法 #### 1. 动态规划方法 动态规划是解决最长回文子串问题的经典方法,其核心思想是通过状态转移表记录子串是否为回文。以下是C语言实现代码: ```c #include <stdio.h> #include <string.h> #include <stdlib.h> char* longestPalindrome(char* s) { int n = strlen(s); if (n == 0) return ""; // 初始化动态规划表 int dp[n][n]; memset(dp, 0, sizeof(dp)); int maxLen = 1, start = 0; // 单个字符均为回文 for (int i = 0; i < n; i++) dp[i][i] = 1; // 检查长度为2的子串 for (int i = 0; i < n - 1; i++) { if (s[i] == s[i + 1]) { dp[i][i + 1] = 1; maxLen = 2; start = i; } } // 检查长度≥3的子串 for (int len = 3; len <= n; len++) { for (int i = 0; i + len - 1 < n; i++) { int j = i + len - 1; if (s[i] == s[j] && dp[i + 1][j - 1]) { dp[i][j] = 1; if (len > maxLen) { maxLen = len; start = i; } } } } // 提取结果 char* result = (char*)malloc((maxLen + 1) * sizeof(char)); strncpy(result, s + start, maxLen); result[maxLen] = '\0'; return result; } int main() { char s[] = "babad"; char* res = longestPalindrome(s); printf("最长回文子串: %s\n", res); // 输出 "bab" 或 "aba" free(res); return 0; } ``` #### 2. Manacher算法(优化方法) Manacher算法通过预处理字符串和中心扩展优化时间复杂度至$O(n)$。以下是简化版实现: ```c void Manacher(char* s, char* result) { // 预处理:插入分隔符(如#) char tmp[2002]; int len = strlen(s); tmp[0] = '$'; for (int i = 0; i <= len; i++) { tmp[2*i + 1] = '#'; tmp[2*i + 2] = s[i]; } int maxRight = 0, center = 0; int p[2002] = {0}; // 存储回文半径 int maxLen = 0, maxCenter = 0; for (int i = 1; tmp[i] != '\0'; i++) { // 利用对称性加速计算 if (i < maxRight) { p[i] = (maxRight - i) < p[2*center - i] ? (maxRight - i) : p[2*center - i]; } // 中心扩展 while (tmp[i - p[i] - 1] == tmp[i + p[i] + 1]) p[i]++; // 更新最右边界 if (i + p[i] > maxRight) { maxRight = i + p[i]; center = i; } // 记录最大值 if (p[i] > maxLen) { maxLen = p[i]; maxCenter = i; } } // 提取结果 int start = (maxCenter - maxLen) / 2; strncpy(result, s + start, maxLen); result[maxLen] = '\0'; } ``` #### 性能对比 | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |--------------|------------|------------|--------------------| | 动态规划 | $O(n^2)$ | $O(n^2)$ | 简单实现,短字符串 | | Manacher算法 | $O(n)$ | $O(n)$ | 长字符串,高效场景 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值