LeetCode5 最长回文子串

最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

题目分析:

(1)暴力解法

首先容易想到暴力解法,可以找出字符串中所有的子串依次判断是否是回文串,此处采用一个稍微简化的方法,假设字符串中的各个字符都可以作为回文串的中心,分奇偶两种情况从各个位置向两侧拓展,利用一个max变量来记录遍历过程中出现过的最长的回文串的长度,用remi记录最长回文串的中心位置,复杂度为O()

代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        //从任一位置分成奇偶两种情况向左右遍历
        int l = s.length();
        int res = 1, remi = 0;
        for(int i = 0; i < l; i++)
        {
            int j, k;
            
            //奇
            j = k = i;
            j--, k++;
            while(j >= 0 && k < l)
            {
                if(s[j] != s[k]) break;
                j--, k++;
            }
            if(res < k - j - 1) res = k - j - 1, remi = j + 1;
            
            //偶
            j = k = i;
            j--;
            while(j >= 0 && k < l)
            {
                if(s[j] != s[k]) break;
                j--, k++;
            }
            if(res < k - j - 1) res = k - j - 1, remi = j + 1;
        }
        string ans = s.substr(remi, res);
        return ans;
    }
};

(2)动态规划

暴力解法的原始思路是判断所有的子串是否是回文串,并从中选取最长的一个,此时有重叠子问题——对于一个在输入串中下标在[i, j] 的子串,其是回文串的必要条件是下标[i+1, j-1]的子串是回文串。

所以设dp[i][j] 为下标[i, j]的回文串长度,当子串为回文串时dp[i][j] = dp[i+1][j-1]+2,否则 dp[i][j] = 0

\[dp[i][j] = \begin{equation} \left\{ \begin{array}{**lr**} =dp[i+1][j-1]+2, &s[i] = s[j] \\ =0 & s[i] ≠ s[j] \end{array} \right. \end{equation} \]

需要在二维进行遍历,时间复杂度也为O()

代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        //动态规划,dp[i][j]表示以i开始到j所构成的回文子串长度,若不是回文串则为0,否则应为j-i+1
        //dp[i][j] = dp[i+1][j-1]+2 (s[i] == s[j] && i+1到j-1构成回文串)
        //           0 (s[i] != s[j] || i+1到j-1构不成回文串)
        int l = s.length();
        int ans = 1, remi = 0;
        int dp[1005][1005];
        for(int i = 0; i < l; i++)
        {
            dp[i][i] = 1;
        }
        for(int i = l - 1; i >= 0; i--)
        {
            for(int j = i+1; j < l; j++)
            {
                if(s[i] == s[j] && dp[i+1][j-1] == j - i - 1) dp[i][j] = dp[i+1][j-1] + 2;
                else dp[i][j] = 0;
                if(ans < dp[i][j]) ans = dp[i][j], remi = i;
            }
        }
        string res = s.substr(remi, ans);
        return res;
    }
};

(3)Manacher算法

对于最长回文串问题,Manacher算法可以在O(n)的时间内解决。

Manacher算法通过插入分隔符将字符串长度设为奇数,然后求以各个字符为中心的回文串长度,以T[i]表示i点到其以自身为中心的回文串的最右端的距离(包含i点自身)。则最长回文子串即为T[i] max - 1

对T[i]的求法:

参考:https://subetter.com/algorithm/manacher-algorithm.html

利用回文串的对称性,利用mx记录到目前为止出现过的回文串可达的最右端,即最大的i+T[i],若i < mx,则说明当前的i作中心的回文串的某一部分已经在求其他T[i]时被扫描过,所以这一部分可以利用回文串的对称性快速求得,在一个回文串中,关于中心对称的位置的字符相同,不难想到完全被包含在某个回文串中的子回文串一定是以成对且子串中心关于整个回文串的中心对称。

所有不被包含在之前已经扫描过的回文串中的部分都需要依次判断,因此输出串的所有字符都被扫描过一次,时间复杂度为O(1)

代码如下:

class Solution {
public:
    string longestPalindrome(string s) {
        //Manacher算法
        int l = s.length();
        string str = "#";
        for(int i = 0; i < l; i++)
        {
            str += s.substr(i, 1) + "#";
        }
        int p[2019];
        l = l * 2 + 1;
        int mx, id;
        mx = id = 0;
        for(int temp = 0; temp < l; temp++) p[temp] = 1;
        int max = 0, remi = 0;
        for(int i = 0; i < l; i++)
        {
            if(i < mx)
            {
                if(p[id*2-i] < mx - i) p[i] = p[id*2-i];
                else p[i] = mx - i;
            }
            else p[i] = 1;
            
            int j = i + p[i];
            while(j < l && 2 * i - j >= 0 && str[2*i-j] == str[j])
            {
            j++;
            p[i]++;
            }
            if(mx < i + p[i]) mx = i + p[i], id = i;
            if(max < p[i]) max = p[i], remi = i;
        }
        string ans = s.substr((remi-max+1)/2, max-1);
        return ans;
    }
};

(4)采用最长公共子串方法出现的错误

最初在看到问题时我首先想到了用最长公共子串的方法,将原字符串和其逆序字符串进行比对,求出最长公共子串,但是出现了问题,例如abcsdcba,求最长公共子串是abc,并不是回文串。如果对每个可能的子串再进行判断,则时间复杂度为O(n³)

posted @ 2019-03-02 22:06  miracleXH  阅读(154)  评论(0编辑  收藏  举报