最长回文子串

题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring

代码及分析

方法一:中心扩散法

回文串即为正反序相同的字符串。
我们可以通过中心扩散的方法判断一个子串是否为回文串,但需要注意的一点是当子串的字符个数奇偶不同时,其对应的中心位置有所不同。例如题中所给的示例:bab为奇数,其中心位置为a,而bb为偶数,其对应位置为bb中间。
建立一个函数,用于对字符串中心扩散,需要两个指针,分别向左右两边进行扩散,当字符个数为偶数时,左右两边起始位置不同,当字符个数为奇数时,左右两边起始位置相同。
为了方便统计更新最长回文子串的起始位置和长度,可以采用引用的方法。

  string longestPalindrome(string s) {
        int l = s.length();
        if(l < 2 )
            return s;
        int len = 0;    //最长回文子串的长度
        int begin = 0;  //最长回文子串开始位置
        for(int i = 0; i < l-1; i++)
        {
            longest_string(s, i, i, begin, len);   // 对应于偶数的情况
            longest_string(s, i, i+1, begin, len); // 对应于奇数的情况
        }
        return s.substr(begin,len);
    }
    void longest_string(string s, int left, int right, int &begin, int &len)
    {
        while(left >= 0 && right <= s.length() && s[left] == s[right])
        {
            left--;
            right++;
        }
        left = left+1;
        right = right-1;
        if(len < (right-left+1))
        {
            begin = left;
            len = right-left+1;
        }
    }
复杂度:

时间复杂度: O(n^2) 遍历整个数组需要O(n)的时间,调用的中心扩散函数需要O(n)的时间故,共需要O(n ^ 2)。
空间复杂度: O(1) 。

补充:当在对数组进行遍历的时候,若当前指针i之后的长度小于当前最大长度len的一半时,后续变没有再继续遍历的必要,故我们可以在遍历时增加条件:

  for(int i = 0; i < l-1; i++)
        {
            if((l-i-1) < len/2)
                break;
            longest_string(s, i, i, begin, len);
            longest_string(s, i, i+1, begin, len);
        }
方法二:动态规划法

该问题也可以用动态规划法进行求解。
建立一个二维的dp数组,其中dp[i][j]表示字符串中i到j是否为回文子串,是则为1, 反之为0;
显然,应该有dp[i][i]=1,当j < i 时不满足条件,无需计算;
对于dp[i][j],当j-i < 2时, 说明 有 i == j 或者 i 和 j 相邻,此时,只需判断 s[i] == s[j];
当j-i > 2时, 除了需要判断s[i] == s[j] 之外,还需判断dp[i+1][j-1] 是否为1。
另外,需要注意的一点是,因为在最后一种情况中,dp[i][j] 的值需要用到dp[i+1][j-1], 故我们不能按照 i 递增的顺序对字符串进行遍历,可以采用 i 递减的顺序进行遍历。

string longestPalindrome(string s) {
        int l = s.length();
        if(l < 2)
            return s;
        int begin;
        int len = 0;
        vector<vector<int>> dp(l,vector<int>(l,0));
        for(int i = 0; i < l; i ++) dp[i][i] = 1;
        for(int i = l-1; i >= 0; i--)
        {
            for(int j = i; j < l; j++)
            {
                if((j-i) < 2)  dp[i][j] = (s[i] == s[j]);
                else if(s[i] == s[j])
                    dp[i][j] = dp[i+1][j-1];
                if(dp[i][j] && (j-i+1) > len)
                {
                    len = j-i+1;
                    begin = i;
                }
                    
            }
        }
        return s.substr(begin,len);
}
复杂度:

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

方法三:Manacher’s Algorithm 马拉车算法

该问题有一个比较经典的解法,Manacher’s Algorithm 马拉车算法。
这个算法理解起来比较复杂,但代码还是很简单的。

string longestPalindrome(string s) {
        int l = s.length();
        if(l < 2)
            return s;
        // 为数组插入字符"#"
        string str = "#";
        for(int i = 0; i < l; i ++)
        {
            str += s[i];
            str += "#";
        }
        int len = str.length();
        vector<int> p(len,0);
        int md = 0, id = 0;
        int maxlen = 0, maxcenter = 0;
        for(int i = 0; i < len; i ++)
        {
            p[i] = md > i ? min(p[2*id-i], md-i) : 1;
            while(i+p[i]<len && i-p[i]>=0&& str[i+p[i]] == str[i-p[i]]) ++p[i];
            if(p[i] >= maxlen)
            {
                maxlen = p[i];
                maxcenter = i;
                
            }
            if(md < i+p[i]-1)
            {
                md = i+p[i]-1;
                id = i;
            }
        }
        return s.substr((maxcenter+1-maxlen)/2,maxlen-1);
    }
复杂度:

时间复杂度: O(n) ;
空间复杂度: O(n) 。

注:我学习这个算法主要参考了leetcode中题解部分的 https://leetcode-cn.com/problems/two-sum/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
以及 https://www.cnblogs.com/grandyang/p/4475985.htm
我觉得这两篇讲解的非常详细,非常值得学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值