leetcode 5. Longest Palindromic Substring 马拉车算法

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example 2:

Input: “cbbd”
Output: “bb”

这题很神的hhhh,有很简单的写法,比如暴力枚举所有子串,然后判断该子串是否是回文串,复杂度是 O ( n 3 ) O(n^3) O(n3)。再比如,以每个位置以及每两个元素间的空隙为中心,向两边延申,边延申边判断是否符合回文的条件,复杂度是 O ( n 2 ) O(n^2) O(n2) (sounds promising!我在下面写一下这种写法,年轻人不要上来就搞那么复杂)

两边延申的代码如下:

class Solution {
public:
    pair<int,int> check(string s,int a,int b){
        int n=s.length();
        while(a>=0&&b<n){
            if(s[a]!=s[b])break;
            a--;b++;
        }
        return make_pair<int,int>(b-a-1,a+1);
    }
    string longestPalindrome(string s) {
        int len=s.length();
        pair<int,int> res=make_pair<int,int>(0,0);
        for(int i=0;i<len;i++){
            pair<int,int> p1=check(s,i,i);
            pair<int,int> p2=check(s,i,i+1);
            if(p1.first>res.first)res=p1;
            if(p2.first>res.first)res=p2;
        }
        return s.substr(res.second,res.first);
    }
};

再或者用动态规划,令 dp[i][j] 表示区间 [i,j] 是否为回文串,复杂度也为 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    bool dp[1005][1005];
    string longestPalindrome(string s) {
        int n=s.length();
        pair<int,int> res=make_pair<int,int>(0,0);
        for(int len=1;len<=n;len++)
            for(int i=0;i+len-1<n;i++){
                int j=i+len-1;
                if(s[i]==s[j]&&(len==1||len==2||dp[i+1][j-1])){
                    if(len==1||len==2)dp[i][j]=true;
                    else dp[i][j]=dp[i+1][j-1];
                    if(len>res.first){
                        res.first=len;
                        res.second=i;
                    }
                }
            }
        return s.substr(res.second,res.first);
    }
};

最后是 Manacher 算法,大致思路是首先把一个字符首位以及中间间隔处填充其他字符,比如把 abdhs 填充成 $#a#b#d#h#s#,设新的串为 t,定义 p[i] 为以位置 i 为中心的回文串的长度,然后维护一个已知最长的回文串的中心坐标 id,以及其对应的右边界 maxrmaxr=id+p[id]),注意此处的边界是开的,也就是说 t[id-p[id]]!=t[id+p[id]],初始化 maxr 为零就行。然后从 1 开始遍历每个位置,根据观察可以发现 p[i] 的值和 i 关于 id 处对称的位置 id-(i-id)=2*id-i 处的值 p[2*id-i] 应该有一些对应关系,其限制条件就是 p[2*id-i] 不能大于 maxr-i 因为大于的部分无法保证依旧对称,因此令 p[i] 的初值为 min(maxr-i,,p[id*2-i]) ,此时便利用了之前已经算过的信息。此算法的复杂度为 O ( n ) O(n) O(n) ,因为在每次循环中只有两种情况:没有更新 maxr,此时说明直接用的 p[id*2-i] 的值,也就是说之前得到的信息完全在范围内,那么再往外的扩展一定是不行的,所以此情况下是 O ( 1 ) O(1) O(1);另一种情况是更新了 maxr,由于 maxr 只能向右移,并且 while 处向外推了多少,maxr 就会向右移多少,总的下来 maxr 最多移 O ( n ) O(n) O(n)。综上,总的复杂度是 O ( n ) O(n) O(n)

另外还需要考虑的是结果的输出,找到最大长度只需要遍历即可,其最大长度应该是 p[i]-1 ,因为 p[i] 这个地方是开的,脑补一下= = ,这个直接就是原串中最长回文串的长度,那怎么找在原串中的起始下标?我是这样考虑的,首先很容易得到 t 中最长回文串的中心下标 i ,首先通过 (i-1)/2 将其映射回原串的中心,然后 (i-1)/2-(p[i]-1)/2 即为起始下标。

class Solution {
public:
    int p[2005];
    string longestPalindrome(string s) {
        int n=s.length();
        string t="$#";
        for(int i=0;i<n;i++){
            t+=s[i];
            t+="#";
        }
        int m=t.length();
        int maxr=0,id;
        for(int i=1;i<m;i++){
            p[i]=i<maxr?min(maxr-i,p[id*2-i]):1;
            while(t[i-p[i]]==t[i+p[i]])p[i]++;
            if(i+p[i]>maxr){
                maxr=i+p[i];
                id=i;
            }
        }
        int res=0,lo=0;
        for(int i=0;i<m;i++)
            if(p[i]-1>res){
                res=p[i]-1;
                lo=(i-1)/2-(p[i]-1)/2;
            }
        return s.substr(lo,res);
    }
};

很优秀:
在这里插入图片描述

另:还看到了一个比较神的做法,复杂度分析起来也是 O ( n 2 ) O(n^2) O(n2) ,但是实际上运算量不大:

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.length();
        if(n==0||n==1)return s;
        int maxl=0,lo=0;
        for(int i=0;i<n;i++){
            int mink=i;
            if(maxl>(n-i-1)*2+1)continue;
            while(i+1<n&&s[i+1]==s[i])i++;
            int maxk=i;
            int curl=maxk-mink+1;
            mink--;maxk++;
            while(mink>=0&&maxk<n&&s[mink]==s[maxk]){
                mink--;maxk++;curl+=2;
            }
            if(curl>maxl)maxl=curl,lo=mink+1;
        }
        return s.substr(lo,maxl);
    }
};

在这里插入图片描述
超快的哦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值