Leetcode 5 / LintCode 200 : Longest Palindromic Substring 最大回文子串

200. Longest Palindromic Substring

Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

Example

Example 1:

Input:"abcdzdcab"
Output:"cdzdc"

Example 2:

Input:"aba"
Output:"aba"

Challenge

O(n2) time is acceptable. Can you do it in O(n) time.

Input test data (one parameter per line)How to understand a testcase?

解法1:

我的解法就是用找最大公共字串+判断是否回文。这里判断很重要,否则s="abcdasdfghjkldcba"时会return "abcd",显然不对,所以我加了isPalindrome()和g_maxPos来判断。另外 ,还有些边边角角的情况需要考虑。比如说s="aaaaaaa"或"aba", 本身就是回文串,直接返回本身。

但这个解法最差情况的时间复杂度为O(n^3),另外还要有额外空间存放reverse string和LCS[n][n]数组,空间复杂度为O(n^2)。虽然通过了,但不是最佳。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

size_t LCS[1001][1001];

bool isPalindrome(string s) {
    string revStr(s);
    reverse(revStr.begin(), revStr.end());
    return (revStr == s);
}

string largestCommonString_variant(string s1, string s2) {
    size_t i=0, j=0;
    size_t len1=s1.size();
    size_t len2=s2.size();
    size_t maxLen=0, maxPos=0;
    size_t g_maxLen=0, g_maxPos=0;

    if (s1=="" || s2=="") return "";
    if (s1==s2) return s1;

    for (i=0; i<len1; ++i) {
        for (j=0; j<len2; ++j) {
            if (s1[i]==s2[j]) {
                if (i==0 || j==0)
                    LCS[i][j]=1;
                else
                    LCS[i][j] = LCS[i-1][j-1]+1;

                if (maxLen < LCS[i][j]) {
                    maxLen = LCS[i][j];
                    maxPos = i-maxLen+1;
                }
            }else {
                LCS[i][j] = 0;
            }

            if (maxLen>g_maxLen) {
                if (isPalindrome(s1.substr(maxPos, maxLen))) {
                    g_maxLen = maxLen;
                    g_maxPos = maxPos;
                }else{
                    maxLen = g_maxLen;
                    maxPos = g_maxPos;
                }
            }
        }
    }

    return s1.substr(g_maxPos, g_maxLen);
}

string longestPalindrome(string s) {
    string reverseStr(s);
    reverse(reverseStr.begin(), reverseStr.end());
    return largestCommonString_variant(s, reverseStr);
}

int main()
{
    string s="a";
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    s="abcdasdfghjkldcba";    //important case
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    s="babad";
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    s="cbbd";
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    s="esbtzjaaijqkgmtaajpsdfiqtvxsgfvijpxrvxgfumsuprzlyvhclgkhccmcnquukivlpnjlfteljvykbddtrpmxzcrdqinsnlsteonhcegtkoszzonkwjevlasgjlcquzuhdmmkhfniozhuphcfkeobturbuoefhmtgcvhlsezvkpgfebbdbhiuwdcftenihseorykdguoqotqyscwymtjejpdzqepjkadtftzwebxwyuqwyeegwxhroaaymusddwnjkvsvrwwsmolmidoybsotaqufhepinkkxicvzrgbgsarmizugbvtzfxghkhthzpuetufqvigmyhmlsgfaaqmmlblxbqxpluhaawqkdluwfirfngbhdkjjyfsxglsnakskcbsyafqpwmwmoxjwlhjduayqyzmpkmrjhbqyhongfdxmuwaqgjkcpatgbrqdllbzodnrifvhcfvgbixbwywanivsdjnbrgskyifgvksadvgzzzuogzcukskjxbohofdimkmyqypyuexypwnjlrfpbtkqyngvxjcwvngmilgwbpcsseoywetatfjijsbcekaixvqreelnlmdonknmxerjjhvmqiztsgjkijjtcyetuygqgsikxctvpxrqtuhxreidhwcklkkjayvqdzqqapgdqaapefzjfngdvjsiiivnkfimqkkucltgavwlakcfyhnpgmqxgfyjziliyqhugphhjtlllgtlcsibfdktzhcfuallqlonbsgyyvvyarvaxmchtyrtkgekkmhejwvsuumhcfcyncgeqtltfmhtlsfswaqpmwpjwgvksvazhwyrzwhyjjdbphhjcmurdcgtbvpkhbkpirhysrpcrntetacyfvgjivhaxgpqhbjahruuejdmaghoaquhiafjqaionbrjbjksxaezosxqmncejjptcksnoq";
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    s="aba";
    cout<<"s="<<s<<endl;
    cout<<longestPalindrome(s)<<endl;

    return 0;
}

解法2:遍历s,每个位置向左右延伸,每次延伸一个单位,直到遇到边界。延伸的同时判断左右新成员是否相等。若否,停止延伸,记录当前最大回文串。

此题要注意:
1) 回文串长度有奇偶两种可能,分别对应"aba"和"abba"。所以要分内部循环又要分两个情况考虑。
2) 延伸长度一开始要设为大于边界,否则savedJ缺省为0就退出,但是对应字符串并非回文。
3) savedJ在第二个内循环前又要初始化,要不然它会被设为第一个内循环的返回值。

该算法时间复杂度为O(n^2)。空间复杂度为O(1)。比较好。

string longestPalindrome(string s) {
    size_t i=0, j=0;
    size_t len=s.size();
    size_t savedJ=len, maxPos=0, maxLen=0;

    if (s=="") return "";

    for (i=0; i<len; ++i) {
        for (j=0; (i+j<len)&&(i-j>=0); ++j) {    //for "babadefg" case
            if (s[i-j]!=s[i+j])
                break;
            savedJ=j;
        }
        if ((maxLen<savedJ*2+1) && (savedJ<len) ) {
            maxPos=i-savedJ;
            maxLen=savedJ*2+1;
        }
        savedJ=len;   //notice! It's important to clear it here!
        for (j=0; (i+j+1<len)&&(i-j>=0); ++j) {   //for "babbacef" case
            if (s[i-j]!=s[i+1+j])
                break;
            savedJ=j;
        }
        if ((maxLen < savedJ*2+2) && (savedJ<len)) {
            maxPos = i-savedJ;
            maxLen = savedJ*2+2;
        }
    }
    return s.substr(maxPos, maxLen);
}

二刷:
 

class Solution {
public:
    /**
     * @param s: input string
     * @return: a string as the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int sLen = s.size();
        if (sLen == 0) return "";
        int gMaxLen = 1, gMaxLenPos = 0;
        for (int i = 0; i < sLen; i++) {
            for (int j = 1; j <= min(i, sLen - i); j++) {
                if (s[i - j] != s[i + j]) break;
                if (gMaxLen < 2 * j + 1) {
                    gMaxLen = 2 * j + 1;
                    gMaxLenPos = i - j;
                }
            }
            if (i < sLen - 1 && s[i] != s[i + 1]) continue;
            if (gMaxLen == 1) {
                gMaxLen = 2;
                gMaxLenPos = i;
            }
            for (int j = 1; j <= min(i, sLen - i - 1); j++) {
                if (s[i - j] != s[i + j + 1]) break;
                if (gMaxLen < 2 * j + 2) {
                    gMaxLen = 2 * j + 2;
                    gMaxLenPos = i - j;
                }
            }
        }
        return s.substr(gMaxLenPos, gMaxLen);
    }
};
class Solution {
public:
    /**
     * @param s: input string
     * @return: a string as the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int sLen = s.size();
        if (sLen == 0) return "";
        int gMaxLen = 1, gMaxLenPos = 0;
        for (int i = 0; i < sLen; i++) {
            int savedJ = 0;
            for (int j = 1; j <= min(i, sLen - i); j++) {
                if (s[i - j] != s[i + j]) break;
                savedJ = j;
            }
            if (gMaxLen < 2 * savedJ + 1) {
                gMaxLen = 2 * savedJ + 1;
                gMaxLenPos = i - savedJ;
            }
            savedJ = 0;
            if (i < sLen - 1 && s[i] != s[i + 1]) continue;
            if (gMaxLen == 1) {
                gMaxLen = 2;
                gMaxLenPos = i;
            }
            for (int j = 1; j <= min(i, sLen - i - 1); j++) {
                if (s[i - j] != s[i + j + 1]) break;
                savedJ = j;  
            }
            if (gMaxLen < 2 * savedJ + 2) {
                gMaxLen = 2 * savedJ + 2;
                gMaxLenPos = i - savedJ;
            }
        }
        return s.substr(gMaxLenPos, gMaxLen);
    }
};



解法3:

注意分"aba"和"abba"两种情况处理。

在findPalindromLen()里面,len增长情况分start==end和start!=end两种情况。前者len+1,后者len+2。

class Solution {
public:
    /**
     * @param s: input string
     * @return: the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int n = s.size();
        int maxLen = 0;
        int maxLenStart = 0;
        for (int i = 0; i < n; ++i) {
            int len1 = findPalindromLen(s, i, i); 
            if (len1 > maxLen) {
                maxLen = len1;
                maxLenStart = i - len1 / 2;  //aba
            }
            
            int len2 = findPalindromLen(s, i, i + 1);
            if (len2 > maxLen) {
                maxLen = len2;
                maxLenStart = i - len2 / 2 + 1;    //abba
            }
        }
        
        return s.substr(maxLenStart, maxLen);
    }

private:
    int findPalindromLen(string & s, int start, int end) {
        int len = 0;
        while(start >= 0 && end < s.size()) {
            if (s[start] == s[end]) {
                len += (start == end) ? 1 : 2;
                start--;
                end++;
            } else {
                break;
            }
        }
        return len;
    }
};

类似的解法(2刷):更简洁。

主要思路是把abc想象成a#b#c。注意跟马拉车算法不一样,那个是把abc变成了#a#b#c#。
当扫描到字符和#时分不同情况处理。

class Solution {
public:
    /**
     * @param s: input string
     * @return: the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int len = s.size();
        int new_len = len * 2 - 1;
        string longest_palindrome;
        int longest_len = 0;
        //assume abc=>a#b#c
        for (int i = 0; i < new_len; ++i) {
            int start = i / 2;
            int end = start + i % 2;
            while(start >= 0 && end < new_len && s[start] == s[end]) {
                if (longest_len < (end - start + 1)) {
                    longest_len = end - start + 1;
                    longest_palindrome = s.substr(start, longest_len);
                }
                start--;
                end++;
            }
        }
        return longest_palindrome;
    }
};


 

解法4:区间型DP。

转移方程为 if (s[j] == s[i])  dp[i][j] = dp[i + 1][j - 1]
注意i循环的顺序。如果i从小到大循环是不对的,因为i=0在i=1之先,所以[0][4]不能用到[1][3]的值,所以input="abcba"的时候,虽然[1][3]是回文串,但是[0][4]没法知道。
所以i应该从大往小循环,i=0的时候,i=1已经执行过了。

class Solution {
public:
    /**
     * @param s: input string
     * @return: the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int n = s.size();
        int maxLen = 0;
        string result;
        
        vector<vector<bool>> dp(n, vector<bool>(n, false));
        
        for (int i = 0; i < n; ++i) dp[i][i] = true;
        result = s.substr(0, 1);
        
        for (int i = 0; i < n - 1; ++i) {
            if (s[i] == s[i + 1]) {
                dp[i][i + 1] = true;
                result = s.substr(i, 2);
            }
        }

        //for (int i = 0; i < n - 1; ++i) {
        for (int i = n - 1; i >= 0; --i) {
            for (int j = i + 2; j < n; ++j) {
                if (s[j] == s[i]) {
                    dp[i][j] = dp[i + 1][j - 1];
                    if (dp[i][j] && maxLen < j - i + 1) {
                        maxLen = j - i + 1;
                        result = s.substr(i, maxLen);
                    }
                }
            }
        }
        
        return result;
    }
};

解法5:著名的马拉车算法。Manancher's Algorithm。

参考资料:Manacher's ALGORITHM: O(n)时间求字符串的最长回文子串 - Felix021 - So far so good

漫画:如何找到字符串中的最长回文子串?

class Solution {
public:
    /**
     * @param s: input string
     * @return: the longest palindromic substring
     */
    string longestPalindrome(string &s) {
        int len_s = s.size();
        if (len_s == 0) return "";
        
        //abc -> #a#b#c#
        string new_s;
        for (int i = 0; i < len_s; ++i) {
            new_s += '#';
            new_s += s[i];
        }
        new_s += '#';
        
        int new_len = new_s.size();
        vector<int> palindromes(new_len, 0);
        int mid = 0, longest = 1;
        palindromes[0] = 1;
        
        for (int i = 1; i < new_len; ++i) {
            int len = 1;
            if (mid + longest > i) {
                int mirror_i = mid - (i - mid);
                len = min(palindromes[mirror_i], mid + longest - i);
            }
            
            while(i + len < new_len && i - len >= 0) {
                if (new_s[i - len] != new_s[i + len]) {
                    break;
                }
                len++;
            }
            
            if (len > longest) {
                longest = len;
                mid = i;
            }
            
            palindromes[i] = len;
        }
        
        longest = longest - 1; // remote the extra #
        int start = (mid - 1) / 2 - (longest - 1) / 2;
        return s.substr(start, longest);
    }
};

马拉车的另一个版本

class Solution {
public:
    string getNewString(string s) {
        string newS = "#";
        for (int i = 0; i < s.size(); i++) {
            newS += s[i];
            newS += '#';
        }
        return newS;
    }
    
    string longestPalindrome(string s) {
        string newS = getNewString(s);
        int *r = new int[newS.size()], c;
        r[0] = 1, c = 0;        
        for (int i = 0; i < newS.size(); i++) {
            if (i >= c + r[c]) {
                r[i] = 1;
            } else {
                //r[i] = min(r[i'], c + r[c] - i);
                r[i] = min(r[2 * c - i], c + r[c] - i);
            }
            //暴力枚举字符串最大长度
            while (i - r[i] >= 0 && i + r[i] < newS.size() && newS[i - r[i]] == newS[i + r[i]]) {
                r[i]++;
            }
            if (c + r[c] < i + r[i]) c = i;   //需要更新了
        }
        
        int ans = 0;
        string res = "";
        for (int i = 0; i < newS.size(); i++) {
            if (r[i] <= ans) continue;
            ans = r[i];
            res = "";
            for (int j = i - r[i] + 1; j <= i + r[i] - 1; j++) {
                if (newS[j] == '#') continue;
                res += newS[j];
            }
        }
        return res;
    }
};

代码同步在

https://github.com/luqian2017/Algorithm

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值