leetcode5.最长回文子串

leetcode5.最长回文子串

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

示例 1:

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

示例 2:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

暴力解决法

结果:超出时间限制

class Solution {
public:
    string longestPalindrome(string s) {
        string res="\0",cur="\0";
        int i=0,j;
        for(i=0;i<s.length();i++){
            for(j=1;i+j<=s.length();j++){
                cur=s.substr(i,j);//从第i个字符开始,截取长度为j
                if(ishuiwen(cur)&&cur.length()>res.length())
                    res=cur;
            }
        }
        return res;
    }
    bool ishuiwen(string s){
        int i=0,j=s.length()-1;
        while(i<=j){
            if(s[i]!=s[j])
                return false;
            i++;
            j--;
        }
        return true;
    }
};

运行结果:

47 / 103 个通过测试用例
状态:超出时间限制
提交时间:0 分钟之前
最后执行的输入:
"zudfweormatjycujjirzjpyrmaxurectxrtqedmmgergwdvjmjtstdhcihacqnothgttgqfywcpgnuvwglvfiuxteopoyizgehkwuvvkqxbnufkcbodlhdmbqyghkojrgokpwdhtdrwmvdegwycecrgjvuexlguayzcammupgeskrvpthrmwqaqsdcgycdupykppiyhwzwcplivjnnvwhqkkxildtyjltklcokcrgqnnwzzeuqioyahqpuskkpbxhvzvqyhlegmoviogzwuiqahiouhnecjwysmtarjjdjqdrkljawzasriouuiqkcwwqsxifbndjmyprdozhwaoibpqrthpcjphgsfbeqrqqoqiqqdicvybzxhklehzzapbvcyleljawowluqgxxwlrymzojshlwkmzwpixgfjljkmwdtjeabgyrpbqyyykmoaqdambpkyyvukalbrzoyoufjqeftniddsfqnilxlplselqatdgjziphvrbokofvuerpsvqmzakbyzxtxvyanvjpfyvyiivqusfrsufjanmfibgrkw

暴力方法由于时间复杂度过高不能Ac

这里采用:用时间换空间的方法

由于每次遇到一个新的子串都需要进行判断,这里考虑用已有的结果进行更新,比如判断s(i,j)是不是回文,可以利用s(i+1,j-1)是不是回文的结果进行判断再判断下s(i)和s(j)是否相等就得出结果了,也是动态规划的一个应用

特别说明:求 长度为 1 和长度为 2 的 P(i,j)P(i,j) 时不能直接用上述方法,因为我们代入公式后会遇到 P[i][j]中 i > j 的情况,比如求 P[1][2] 的话,我们需要知道 P[1+1][2-1]=P[2][1],而 P[2][1] 代表着 S[2,1] 是不是回文串,显然是不对的,所以我们需要单独判断。

参考:
作者:windliang
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-bao-gu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

完整代码:

/*对暴力方法的优化
*/
#include

using namespace std;

string longestPalindrome(string s){
    string res="";
    int i,j,l=s.length(),end,max=0;
    if(l==0||l==1)
        return s;
    int** cell;
    cell=new int*[l];//初始化二维数组的长度,也就是行数
    for(i=0;i<l;i++)
        cell[i]=new int[l];//初始化二维数组的列数
    
    for(i=1;i<=l;i++){//控制子串的长度
       for(j=0;i+j<=l;j++){
           end=j+i-1;
           
           cell[j][end]=(i==1||i==2||cell[j+1][end-1])&&(s[j]==s[end]);
           if(i>=max&&cell[j][end]==1){
               max=i;
               res=s.substr(j,i);
           }
       }
    }
    return res;
}

int main(){
    string s;
    cin>>s;
    cout<<longestPalindrome(s);
    return 0;
}

运行结果
运行超时,依然有几个测试用例没有通过

98 / 103 个通过测试用例
状态:超出时间限制
提交时间:0 分钟之前
最后执行的输入:
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

动态规划

本题中求字符串原序和逆序的最长公共子串,但是需要特别注意的一点是:

所求的最长公共字串不一定是回文,比如说:abc345cba,逆串:abc543cba,最长公共子串为abc,但他不是回文,
原因在于:该子串在逆串中的序(0 1 2)和原串中的序(0 1 2)对不上,

举一个能对的上的例子:caba,逆串:abac,最长公共子串:aba,该子串在逆序中的序(0 1 2),原串中的序(3 2 1),逆序中的0恰好对应到原序中的1

所以求得最长子串后需要判断:
判断方法:逆序中的第一个字符,是否恰好对应到原序中的最后一个字符,如果是则更新,否则不更新

最长公共子串
构造二维矩阵的方法:
行、列分别是需要判断的两个字符串
f ( n ) = { 斜 对 角 的 值 c e l l [ i − 1 ] [ j − 1 ] + 1 , 当前比较的两个字符相同 0 , 当前比较的两个字符不相等 f(n)= \begin{cases} 斜对角的值cell[i-1][j-1]+1, & \text {当前比较的两个字符相同} \\ 0, & \text{当前比较的两个字符不相等} \end{cases} f(n)={cell[i1][j1]+1,0,当前比较的两个字符相同当前比较的两个字符不相等

/*动态规划实现最长回文子序列
借助最长前缀匹配来实现*/
#include

using namespace std;

string longestPalindrome(string s){
    if(s.length()==0||s.length()==1)
        return s;
    string pre_s=s,res="";//字符串原序
    reverse(s.begin(),s.end());//s字符串翻转后的序列
    int l=s.length(),i,j,max_i=0,max_j=0;
    int** cell;
    cell=new int*[l];//初始化二维数组的长度,也就是行数
    for(i=0;i<l;i++)
        cell[i]=new int[l];//初始化二维数组的列数
    for(i=0;i<l;i++){
        for(j=0;j<l;j++){
            if(pre_s[i]==s[j]){
                if(i>0&&j>0){
                    cell[i][j]=cell[i-1][j-1]+1;
                }
                else{
                    cell[i][j]=1;
                }
            }
            else{
                cell[i][j]=0;
            }
            //判断该最长字串是否是回文
            //查看在逆转串中子串第一个字符是否是原串中的子串最后一个字符逆转来的
            //也就是找逆转串中的子串第一个字符在原串中的位置是否和原串中子串的最后一个字符的位置是否相同
            if(cell[i][j]>=cell[max_i][max_j]){//开始写的是>,后来改成了>=以方便处理第一个字符和最后一个字符相等,第一个字符是回文的情况,abcda
                max_i=i;                       //或者出现cell数组中的值相等,但不一定都是满足回文的情况
                max_j=j;
                if(l-max_j-1+cell[max_i][max_j]-1==max_i)//只有当是回文串的时候才会进行更新操作
                    res=pre_s.substr(max_i-cell[max_i][max_j]+1,cell[max_i][max_j]);
            }
        }
    }
    
    return res;
    


}

int main(){
    string s;
    cin>>s;
    cout<<longestPalindrome(s);
    return 0;
}

运行结果:有一个测试用例没有通过
原因在于:max_i,max_j这里出现了问题,按上述代码会出现更新max_i,max_j但不会更新res的情况,如果res[max_i][max_j]=3,这时候这个子串并不是回文串就会出现:当后面有长度小于3的回文串出现时,无法更新的情况
输入为:abcdbbfcba
对应矩阵:
a b c f b b d c b a a 1 0 0 0 0 0 0 0 0 1 b 0 2 0 0 1 1 0 0 1 0 c 0 0 3 0 0 0 0 1 0 0 d 0 0 0 0 0 0 1 0 0 0 b 0 1 0 0 1 1 0 0 1 0 b 0 1 0 0 1 2 0 0 1 0 f c b a \begin{array}{c|lcr} & \text{a}&\text{b}&\text{c}&\text{f}&\text{b}&\text{b}&\text{d}&\text{c}& \text{b}& \text{a}\\ \hline a &1&0&0&0&0&0&0&0&0&1 \\ b &0&2&0&0&1&1&0&0&1&0 \\ c &0&0&3&0&0&0&0&1&0&0\\ d&0&0&0&0&0&0&1&0&0&0\\ b&0&1&0&0&1&1&0&0&1&0\\ b&0&1&0&0&1&2&0&0&1&0\\ f\\ c\\ b\\ a\\ \end{array} abcdbbfcbaa100000b020011c003000f000000b010011b010012d000100c001000b010011a100000

提交记录
102 / 103 个通过测试用例
状态:解答错误
提交时间:3 分钟之前
输入:
"abcdbbfcba"
输出:
"a"
预期:
"bb"

修改后的代码

/*动态规划实现最长回文子序列
借助最长前缀匹配来实现*/
#include

using namespace std;

string longestPalindrome(string s){
    if(s.length()==0||s.length()==1)
        return s;
    string pre_s=s,res="";//字符串原序
    reverse(s.begin(),s.end());//s字符串翻转后的序列
    int l=s.length(),i,j,max_i=0,max_j=0;
    int** cell;
    cell=new int*[l];//初始化二维数组的长度,也就是行数
    for(i=0;i<l;i++)
        cell[i]=new int[l];//初始化二维数组的列数
    for(i=0;i<l;i++){
        for(j=0;j<l;j++){
            if(pre_s[i]==s[j]){
                if(i>0&&j>0){
                    cell[i][j]=cell[i-1][j-1]+1;
                }
                else{
                    cell[i][j]=1;
                }
            }
            else{
                cell[i][j]=0;
            }
            //判断该最长字串是否是回文
            //查看在逆转串中子串第一个字符是否是原串中的子串最后一个字符逆转来的
            //也就是找逆转串中的子串第一个字符在原串中的位置是否和原串中子串的最后一个字符的位置是否相同
            //回文串时才更新
            if(cell[i][j]>=cell[max_i][max_j]&&(l-j-1+cell[i][j]-1==i)){//开始写的是>,后来改成了>=以方便处理第一个字符和最后一个字符相等,第一个字符是回文的情况,abcda,或者出现cell数组中的值相等,但不一定都是满足回文的情况
            //l-j-1逆串中的最后一个对应到原创中的第一个字符的位置
            //l-j+1+cell[i][j]-1,再加上长度就是对应原串中的最后一个字符
                max_i=i;                       
                max_j=j;                
                res=pre_s.substr(max_i-cell[max_i][max_j]+1,cell[max_i][max_j]);
            }
        }
    }
    
    return res;
    


}

int main(){
    string s;
    cin>>s;
    cout<<longestPalindrome(s);
    return 0;
}

运行结果:

执行结果:
通过
显示详情
执行用时 :540 ms, 在所有 cpp 提交中击败了8.09%的用户
内存消耗 :191.7 MB, 在所有 cpp 提交中击败了5.06%的用户

优化

在上一个代码的基础上,进行空间复杂度的优化,这里要特别说明一点:二维矩阵的更新,可以按行更新,更新完第i-1行再更新第i行,也可以按列更新。

我们这里采用按行更新的方式,更新第i行需要用到第i-1行的数据,类似于cell[4]=cell[3]+1,所以j需要从l递减到0,不能从0开始到l,这样就把上一行的数据覆盖了。

/*动态规划实现最长回文子序列
借助最长前缀匹配来实现
在上一个代码的基础上进行优化,降低时间复杂度*/
#include

using namespace std;

string longestPalindrome(string s){
    if(s.length()==0||s.length()==1)
        return s;
    string pre_s=s,res="";//字符串原序
    reverse(s.begin(),s.end());//s字符串翻转后的序列
    int l=s.length(),i,j,max=0;
    int* cell;
    cell=new int[l];//初始化数组的长度,也就是列数
    
    for(i=0;i<l;i++){
        for(j=l-1;j>=0;j--){
            if(pre_s[i]==s[j]){
                if(i>0&&j>0){
                    cell[j]=cell[j-1]+1;
                }
                else{
                    cell[j]=1;
                }
            }
            else{
                cell[j]=0;
            }
            //判断该最长字串是否是回文
            //查看在逆转串中子串第一个字符是否是原串中的子串最后一个字符逆转来的
            //也就是找逆转串中的子串第一个字符在原串中的位置是否和原串中子串的最后一个字符的位置是否相同
            //回文串时才更新
            if(cell[j]>=max&&(l-j-1+cell[j]-1==i)){//开始写的是>,后来改成了>=以方便处理第一个字符和最后一个字符相等,第一个字符是回文的情况,abcda,或者出现cell数组中的值相等,但不一定都是满足回文的情况
                                       
                max=cell[j];                
                res=pre_s.substr(i-max+1,max);
            }
        }
    }
    
    return res;
    


}

int main(){
    string s;
    cin>>s;
    cout<<longestPalindrome(s);
    return 0;
}

运行结果:
所占用的内存明显减小了,是上一个代码的十分之一

执行结果:
通过
显示详情
执行用时 :352 ms, 在所有 cpp 提交中击败了18.78%的用户
内存消耗 :16.3 MB, 在所有 cpp 提交中击败了42.19%的用户

中心扩展法

从该字符开始向两边扩展,直到不是回文时,返回
这里要特别注意回文串长度为奇数和偶数的两种不同情况
奇数:中心是一个字符
偶数:中心是两个字符

#include

using namespace std;

int expandAroundCenter(string s,int left,int right){
    while(left>=0 && right<s.length() && s[left]==s[right]){
        --left;
        ++right;
    }
    return right-left-1;
}

string longestPalindrome(string s){
    string res="";
    int i,l=s.length(),len1,len2,max=0;
    for(i=0;i<l;i++){
        len1=expandAroundCenter(s,i,i);
        len2=expandAroundCenter(s,i,i+1);
        if(len1>max||len2>max){
            max=len1>len2?len1:len2;//子串的最大长度
            res=s.substr(i-(max-1)/2,max);//i指向的是子串中心的那个字符,或者是中心偏左的字符
        }
    }
    return res;
}

int main(){
    string s;
    cin>>s;
    cout<<longestPalindrome(s);
    return 0;
}

二刷
中心扩展:

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

动态规划:

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.length() == 0)
            return "";
        vector<vector<int>> dp(s.length(), vector<int>(s.length(), 0));
        int x = 0, y = 0;
        for(int len = 1; len <= s.length(); ++len){
            for(int i = 0; i + len - 1 < s.length(); ++i){
                int end = i + len - 1;
                if((s[i] == s[end]) && (len == 1 || len == 2 || dp[i + 1][end - 1]))
                    dp[i][end] = len;
                if(dp[i][end] > dp[x][y]){
                    x = i;
                    y = end;
                }
            }
        }
        return s.substr(x, y - x + 1);
    }
};

你可能感兴趣的:(#,字符串)