极客

我的博客网站:https://www.huangyiblog.com/ 我的github帐号:https://github.com/Mrhuangyi

最长回文子串

最长回文子串

问题描述:
给出一个字符串S,求S的最长回文子串的长度。

暴力解法

枚举子串的两个端点i和j,判断[i,j]区间内的子串是否回文。
其中枚举端点需要O(n^2),判断回文需要O(n)因此总的复杂度为O(n^3)

动态规划解法

时间复杂度为O(n^2)
令dp[i][j]表示S[i]至S[j]所表示的子串是否是回文子串,是则为1,不是为0.
根据S[i]是否等于S[j],将转移情况分为两类:
1. 若S[i]==S[j],只要S[i+1]到S[j-1]是回文子串,那么S[i]到S[j]就是回文子串;
否则,就不是。
2. 若S[i]!=S[j]那么S[i]到S[j]一定不是回文子串。
由此得出状态转移方程:

dp[i][j]= dp[i+1][j-1],S[i]==S[j]
0,S[i]!=S[j]
边界:dp[i][i] = 1,dp[i][i+1] = (S[i]==S[i+1)?1:0

但是不能按照i和j从小到大进行枚举子串两个端点然后更新dp[i][j],因为无法保证
dp[i+1][j-1]已经被计算过,从而无法得到正确的dp[i][j]

由于边界表示的是长度为1和2的子串,且每次转移时都对子串的长度减1,因此考虑按子串的长度和子串的初始位置进行枚举
即第一遍将长度为3的子串的dp值全部求出,第二遍通过第一遍结果计算长度为4的子串的值

#include<cstdio>
#include<cstring>
const int maxn = 1010;
char S[maxn];
int dp[maxn][maxn];
int main()
{
    gets(S);
    int len = strlen(S),ans = 1;
    memset(dp,0,sizeof(dp));
    for(int i=0;i<len;i++){
        dp[i][i] = 1;
        if(i<len-1){
            if(S[i]==S[i+1]){
                dp[i][i+1] = 1;
                ans = 2;
            }
        }
    }
    for(int t=3;t<=len;t++){
        for(int i=0;i+t-1<len;i++){
            int j = i+t-1;
            if(S[i]==S[j]&&dp[i+1][j-1]==1){
                dp[i][j] = 1;
                ans = t;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

字符串哈希+二分解法

时间复杂度为O(nlogn)
对给定的字符串str,可以先求出其字符串hash数组h1,然后再将str反转,
求出反转字符串rstr的hash数组h2,接着按回文串的奇偶情况讨论
- 回文串的长度是奇数:枚举回文中心点i,二分子串的半径k,找到最大的使子串[i-k,i+k]是回文串的k。

  • 回文串的长度是偶数:枚举回文空隙点,令i表示空隙左边第一个元素的下标,二分子串的半径k,找到最大的使子串
    [i-k+1,i+k]是回文串的k。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
const ll p = 10000019;
const ll maxn = 200010;
ll powp[maxn],h1[maxn],h2[maxn];
void init(){
    powp[0] = 1;
    for(int i=1;i<maxn;i++){
        powp[i] = (powp[i-1]*p)%mod;
    }
}
void calh(ll h[],string &str){
    h[0] = str[0];
    for(int i=1;i<str.length();i++){
        h[i] = (h[i-1]*p+str[i])%mod;
    }
}
int calSingleSubh(ll h[],int i,int j){
    if(i==0) return h[j];
    return ((h[j]-h[i-1]*powp[j-i+1])%mod+mod)%mod;
}
int binarySearch(int l,int r,int len,int i,int isEven){
    while(l<r){
        int mid = (l+r)/2;
        int h1l = i-mid+isEven,h1r = i;
        int h2l = len-1-(i+mid),h2r = len-1-(i+isEven);
        int hashl = calSingleSubh(h1,h1l,h1r);
        int hashr = calSingleSubh(h2,h2l,h2r);
        if(hashl!=hashr) r=mid;
        else l=mid+1;
    }
    return l-1;
}
int main()
{
    init();
    string str;
    getline(cin,str);
    calh(h1,str);
    reverse(str.begin(),str.end());
    calh(h2,str);
    int ans = 0;
    for(int i=0;i<str.length();i++){
        int maxlen = min(i,(int)str.length()-1-i)+1;
        int k = binarySearch(0,maxlen,str.length(),i,0);
        ans = max(ans,k*2+1);
    }
    for(int i=0;i<str.length();i++){
        int maxlen = min(i+1,(int)str.length()-1-i)+1;
        int k = binarySearch(0,maxlen,str.length(),i,1);
        ans = max(ans,k*2);
    }
    printf("%d\n",ans);
    return 0;
}

Manacher算法

时间复杂度为O(n)
算法分析:
由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里)。

举个例子:s=”abbahopxpo”,转换为s_new=”#a#b#b#a#h#o#p#x#p#o#"(这里的字符 只是为了防止越界,下面代码会有说明),如此,s 里起初有一个偶回文abba和一个奇回文opxpo,被转换为#a#b#b#a#和#o#p#x#p#o#,长度都转换成了奇数。

定义一个辅助数组int p[],其中p[i]表示以 i 为中心的最长回文的半径
p[i] - 1正好是原字符串中最长回文串的长度。

Alt txt
设置两个变量,mx 和 id 。mx 代表以 id 为中心的最长回文的右边界,也就是mx = id + p[id]。

假设我们现在求p[i],也就是以 i 为中心的最长回文半径,如果i < mx,如上图,那么:

if (i < mx)  
    p[i] = min(p[2 * id - i], mx - i);

2 * id - i为 i 关于 id 的对称点,即上图的 j 点,而p[j]表示以 j 为中心的最长回文半径,因此我们可以利用p[j]来加快查找。

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

void findBMstr(string& str)
{
    int *p = new int[str.size() + 1];
    memset(p, 0, sizeof(p));

    int mx = 0, id = 0;
    for(int i = 1; i <=  str.size(); i++)
    {
        if(mx > i)
        {
            p[i] = (p[2*id - i] < (mx - i) ? p[2*id - i] : (mx - i));
        }
        else
        {
            p[i] = 1;
        }

        while(str[i - p[i]] == str[i + p[i]])
            p[i]++;

        if(i + p[i] > mx)
        {
            mx = i + p[i];
            id = i;
        }

    }
    int max = 0, ii;
    for(int i = 1; i < str.size(); i++)
    {
        if(p[i] > max)
        {
            ii = i;
            max = p[i];
        }
    }

    max--;

    int start = ii - max ;
    int end = ii + max;
    for(int i = start; i <= end; i++)
    {
        if(str[i] != '#')
        {
            cout << str[i];
        }
    }
    cout << endl;

    delete  p;
}

int main()
{
    string str = "12212321";
    string str0;
    str0 += "$#";
    for(int i = 0; i < str.size(); i++)
    {
        str0 += str[i];
        str0 += "#";
    }

    cout << str0 << endl;
    findBMstr(str0);
    return 0;
}
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hy971216/article/details/80322690
个人分类: dp 字符串
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

最长回文子串

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭