leetcode 5. 最长回文子串

问题描述

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

代码1(动态规划)

使用动态规划,定义

\[dp[i][j] = \begin{cases} 1 \quad \text{ 以第i个元素开头第j个元素结尾的字符串是回文子串}\\ 0 \quad \text{ 以第i个元素开头第j个元素结尾的字符串不是回文子串} \end{cases} \]

显然单个字符一定是回文子串,连着的两个字符如果相等也是回文子串,因此可以只考虑长度大于2的回文子串的情况。

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        if(n == 0 || &s == NULL) return "";
        if(n == 1) return s;
        int L,i,j,k,start;
        vector<vector<int>> dp(n,vector<int>(n,0));
        //int dp[n][n] = {0};
        //初始化边界
        for(i = 0; i < n; i++)
        {
            dp[i][i] = 1;
            start = i;
            L = 1;
        }
        for(i = 1; i < n;i++)
        {
            if(s[i] == s[i-1])
            {
                dp[i-1][i] = 1;
                start = i-1;//进行赋值,防止没有长度大于3的回文子串
                L = 2;
            }
        }
        //正式求解,初始化了长度为1和2的情况,只需要找大于2的情况
        for(i = 3; i <= n; i++)//这里的i代表了长度
        {
            for(j = 0; j + i -1 < n; j++)
            {
                k = j+i-1;
                if(s[k] == s[j] && dp[j+1][k-1] == 1)
                {
                    start = j;
                    L = i;
                    dp[j][k] = 1;
                }
            }
        }
        return s.substr(start,L);
    }
};

结果:

执行用时 :376 ms, 在所有 C++ 提交中击败了12.73%的用户
内存消耗 :186.6 MB, 在所有 C++ 提交中击败了6.59%的用户

我们用

int dp[n][n] = {0};

代替原先的

vector<vector<int>> dp(n,vector<int>(n,0));

后,速度提高了一倍:

执行用时 :176 ms, 在所有 C++ 提交中击败了41.51%的用户
内存消耗 :13.3 MB, 在所有 C++ 提交中击败了56.59%的用户

考虑如何将空间复杂度降为\(O(N)\).定义\(dp[i]=j\)以i个字符开头j个字符结尾的子串为回文子串,显然初始化有\(dp[i]=i\),单独一个字符必定是回文子串。

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        if(n == 0 || &s == NULL) return "";
        if(n == 1) return s;
        int i,left,right,start,len;
        int dp[n];
        dp[n-1] = n-1;
        for(i = 0; i < n; i++)
        {
            if(s[i] != s[i+1])
            {
                dp[i] = i;
            }
            else 
            {
                dp[i] = i+1;
            }
        }
        for(i = 2; i <= n; i++)
        {
            for(left = 0; left+i-1 < n; left++)
            {
                right = left + i - 1;
                if(s[left]==s[right]&&(dp[left+1]==right-1))
                {
                    dp[left] = right;
                }
            }
        }
        len = 0;
        for(i = 0; i < n; i++)
        {
            if(dp[i]-i+1>len)
            {
                len = dp[i]-i+1;
                start = i;
            }
        }
        return s.substr(start,len);
    }
};

但是只能求解99/103个问题,在遇到"ccc"时求解失败,留待后面考虑。

代码2(中心扩展算法)

回文的中心要区分单双。假如回文的中心为 双数,例如 abba,那么可以划分为 ab bb ba,对于n长度的字符串,这样的划分有 n-1 种。假如回文的中心为 单数,例如 abcd, 那么可以划分为 a b c d, 对于n长度的字符串,这样的划分有 n 种。对于 n 长度的字符串,我们其实不知道它的回文串中心倒底是单数还是双数,所以我们要对这两种情况都做遍历,也就是 n+(n-1) = 2n - 1.

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        if(n == 0 || &s == NULL) return "";
        if(n == 1) return s;
        int start = 0,len = 1,i,left,right;
        for(i = 0; i < n-1; i++)
        {
            if(s[i] == s[i+1])
                check(s,i,i+1,start,len);
            check(s,i,i,start,len);
        }     
        return s.substr(start,len);
    }
    void check(string s,int left,int right,int &start,int &len)
    {
        int i, n = s.size(), step = 1;
        while(left-step >= 0 && right+step < n)
        {
            if(s[left-step] == s[right+step])
                 ++step;
            else
                break;
        }
        int wide = 2*step + right - left - 1;
        if(wide > len)
        {
            start = left - step + 1;
            len = wide;
        }
    }
};

结果:

执行用时 :44 ms, 在所有 C++ 提交中击败了71.80%的用户
内存消耗 :76 MB, 在所有 C++ 提交中击败了35.90%的用户

代码3(马拉车算法)

这是一个\(O(N)\)级别的算法,参见博客.

posted @ 2020-03-05 08:40  曲径通霄  阅读(191)  评论(0编辑  收藏  举报