LeetCode 5. 最长回文子串

题目描述

5. 最长回文子串

解法一:暴解,超时了(C++实现)

class Solution {
public: 
    bool isPalindromic(string s){
        int len = s.length();
         for(int i=0;i<len/2;i++)
         {
            if(s.at(i)!=s.at(len-i-1)) return false;
         }
         return true;
    }

public:
    string longestPalindrome(string s) {
        string ans="";
        int maxlen = 0;
        int len = s.length();
        for(int i=0;i<len;i++)
        {
            for(int j=i+1;j<=len;j++)
            {
                string test = s.substr(i, j-i+1);
                if(isPalindromic(test)&&test.length()>maxlen)
                {
                    ans = s.substr(i, j-i+1);
                    int ans_len = ans.length();
                    maxlen = max(maxlen, ans_len);
                }
            }
        }
        return ans;
    }
};

讲几个知识点:

  1. C++提取子串可以参考:C++中在字符串中提取子字符串
  2. C++中bool和boolean的区别:
    bool是c++中的一个关键字,属于c++类型的一种
    boolean是定义来的
    typedef unsigned char boolean; #define true 1 #define false 0
  3. string.at(n) 返回下标为n的元素的引用

解法二:中心扩展法(C++实现)

我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 1 个这样的中心。

你可能会问,为什么会是 2n - 1 个,而不是 n 个中心?

因为回文的中心要区分单双。

假如回文的中心为 双数,例如 abba,那么可以划分为 ab bb ba,对于n长度的字符串,这样的划分有 n-1 种。

假为回文的中心为 单数,例如 abcd, 那么可以划分为 a b c d, 对于n长度的字符串,这样的划分有 n 种

对于 n 长度的字符串,我们其实不知道它的回文串中心倒底是单数还是双数,所以我们要对这两种情况都做遍历,也就是 n+(n-1) = 2n - 1,所以时间复杂度为 O ( n ) O(n) O(n)

当中心确定后,我们要围绕这个中心来扩展回文,那么最长的回文可能是整个字符串,所以时间复杂度为 O ( n ) O(n) O(n)

所以总时间复杂度为 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    string longestPalindrome(string s) {
        if(!s.length()) return s;
        
        int start = 0, end = 0;
        for(int i=0;i<s.length();i++)
        {
            int len1 = expandAroundCenter(s,i, i); // 一个元素为中心
            int len2 = expandAroundCenter(s, i, i+1); // 两个元素为中心
            int len = max(len1, len2);
            if(len>end-start+1)
            {
                start = i-(len-1)/2;
                end = i+len/2;
            }
        }
        return s.substr(start, end-start+1);
    }

    int expandAroundCenter(string s, int left, int right){
        int l = left, r = right;
        while(l>=0 && r<s.length() && s[l]==s[r])
        {
            l--;
            r++;
        }
        return r-l-1;
    }
};
class Solution {
public:
    string longestPalindrome(string s) {
        string res = "";
        for (int i=0; i < s.size(); i++)
        {
            string s1 = palindrome(s, i, i);
            string s2 = palindrome(s, i, i + 1);
            res = res.size() > s1.size() ? res : s1;
            res = res.size() > s2.size() ? res : s2;

        }
        return res;
    }

    string palindrome(string s, int l, int r){
        while (l >= 0 && r < s.size() && s[l] == s[r])
        {
            l--;
            r++;
        }
        return s.substr(l + 1, r - l - 1);
    }
};

解法三:Manacher算法(C++实现)

可以参考

Manacher马拉车算法

class Solution {
public:
    string longestPalindrome(string s) {
    	//插入"#"
        string s1 ="$#";
        for(auto c:s)
        {
            s1 += c;
            s1 += '#';
        }
        
        vector<int> p(s1.length(),0);
        
        //maxight表示某个回文串延伸在最右端半径的下标,center表示这个回文子串最中间位置下标
    	//resLen表示对应在s中的最大子回文串的半径,resCenter表示最大子回文串的中间位置
        int maxright = 0, center = 0, reslen = 0, rescenter = 0;
        
        //建立p数组
        for(int i=1;i<s1.length();i++)
        {
        	//更新p[i]的值
            p[i] = maxright>i?min(p[2*center-i],maxright-i):1;
            
            //遇到三种特殊的情况,都需要利用中心扩展法
            while(s1[i+p[i]]==s1[i-p[i]]) p[i]++;
            
            //半径下标i+p[i]超过边界mx,需要更新
            if(maxright<i+p[i]){
                maxright = i+p[i];
                center = i;
            }
            
            //更新最大回文子串的信息
            if(reslen<p[i]){
                reslen = p[i];
                rescenter = i;
            }
        }
        
        //最长回文子串长度为半径-1,起始位置为中间位置减去半径再除以2
        return s.substr((rescenter-reslen)/2, reslen-1);
    }
};

复杂度分析:

时间复杂度: O ( N ) O(N) O(N),由于 Manacher 算法只有在遇到还未匹配的位置时才进行匹配,已经匹配过的位置不再匹配,因此对于字符串 s 的每一个位置,都只进行一次匹配,算法的复杂度为 O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N)

讲几个知识点:

  1. 关于 auto 的内容参见 C++auto关键字的使用
  2. 关于 vector 定义的内容参见 C++基础之向量Vector

解法四:线性规划(C++)

我们给出 P ( i , j ) P(i,j) P(i,j) 的定义如下:

P ( i , j ) = { true, 如果子串 S i … S j 是回文子串 false, 其它情况 P(i,j) = \begin{cases} \text{true,} &\quad\text{如果子串} S_i \dots S_j \text{是回文子串}\\ \text{false,} &\quad\text{其它情况} \end{cases} P(i,j)={true,false,如果子串SiSj是回文子串其它情况

因此, P ( i , j ) = ( P ( i + 1 , j − 1 )  and  S i = = S j ) P(i, j) = ( P(i+1, j-1) \text{ and } S_i == S_j ) P(i,j)=(P(i+1,j1) and Si==Sj)

基本示例如下:

P ( i , i ) = t r u e P(i, i) = true P(i,i)=true
P ( i , i + 1 ) = ( S i = = S i + 1 ) P(i, i+1) = ( S_i == S_{i+1} ) P(i,i+1)=(Si==Si+1)

这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…

复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.length()<=1) return s;
        
        int strlen = s.length();
        int maxlen = 1;
        string ans; 
        ans= s[0];

        vector<vector<bool>> dp(strlen, vector<bool>(strlen));
        for(int j=0; j<strlen;j++)
        {
            for(int i=j;i>=0;i--)
            {
                if((s[i]==s[j]) && ((j-i<=2) || dp[i+1][j-1]))
                {
                    dp[i][j] = true;
                    if(j-i+1>maxlen)
                    {
                        maxlen = j-i+1;
                        ans = s.substr(i, maxlen);
                    }
                }
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值