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;
}
};
代码同步在