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.
找字符串中最大的回文字符串。回文的判断就是两头往中间两两判断,回文的寻找,最常见的就是逐一检索,同时以每次字符为中间向两侧拓展判断,注意分奇偶讨论可能存在的子序列。该方法如下:
方法一:以每个字符为中心逐一分奇偶检索O(n^2)
string longestPalindrome(string s) {
int len = s.size();
if (len == 0) return NULL;
if (len == 1) return s;
vector<int> count(len, 0);
int maxpal = 1, curpal;
int i, j, tempi, tempj;
for (i = 0; i < len; i++) {
// 偶数回文的情况
if (i + 1 < len && s[i] == s[i+1]) {
tempi = i;
tempj = i+1;
curpal = 2;
while (tempi >= 0 && tempj < len) {
if (s[tempi] != s[tempj]) break;
else {
curpal = max(curpal, tempj-tempi+1);
tempi--;
tempj++;
}
}
count[tempi+1] = max(count[tempi+1], curpal);
maxpal = max(maxpal, count[tempi+1]);
}
//奇数回文的情况
if (i + 2 < len && s[i] == s[i+2]) {
tempi = i;
tempj = i+2;
curpal = 3;
while (tempi >= 0 && tempj < len) {
if (s[tempi] != s[tempj]) break;
else {
curpal = max(curpal, tempj-tempi+1);
tempi--;
tempj++;
}
}
count[tempi+1] = max(count[tempi+1], curpal);
maxpal = max(maxpal, count[tempi+1]);
}
}
string res;
//在count里我们存了每个位置的最大回文长度,同时我们得到了全局最大回文长度
for (i = 0; i < len; i++) {
if (count[i] == maxpal) {
res = s.substr(i, maxpal);
}
}
return res;
}
方法二:动态规划O(n^2)
我们建立一个二维dp数组,dp[len][len],len为s.size()。dp[i][j]表示从数组里的i到j是否是回文数组。true or false。
递推关系为:
当s[i] == s[j]:
如果j-i<=2时,dp[i][j] = true;
如若不是,dp[i+1][j-1]为true才可使dp[i][j]为true。
代码如下:
string longestPalindrome(string s) {
int len = s.size();
string res;
if (len == 0) return res;
int i, j, start, maxpal = 1;
bool dp[len][len];
for (i = len - 1; i >= 0; i--) {
for (j = i; j < len; j++) {
if (s[j] == s[i] && (j-i<=2 || dp[i+1][j-1])) {
dp[i][j] = true;
if (j-i+1 >= maxpal) {
maxpal = j - i + 1;
start = i;
}
}
else dp[i][j] = false;
}
}
res = s.substr(start, maxpal);
return res;
}
方法三: Manacher’s algorithm(马拉车算法)O(n)
这种方法是Manacher在1975年提出的,具体解释大家可以参考博客:
Manacher’s Algorithm,其中核心公式代码为:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
mx是当前最长回文的右边界,id为它的中心,这里利用新构造的序列t将存在的回文都置为奇数。所以一定有一个中心。然后p[i]表示当前检索的t序列中的i位的回文半径,i一定在id和mx中间(因为id随着i更新,一定小于等于i)。所以这个算法巧妙的利用了已知回文结构的对称性,给出了p[i]的可以预知的最小值。同时省去了奇偶判断。十分巧妙。具体代码如下:
string longestPalindrome(string s) {
int len = s.size();
string t = "$#";
int i;
// insert # to be t
for (i = 0; i < len; i++) {
t += s[i];
t += '#';
}
// process t
int p[t.size()];
int resCenter = 0, resLen = 0, mx = 0, id = 0;
for (i = 1; i < t.size(); i++) {
p[i] = mx > i ? min(p[2*id - i], mx - i) : 1;
while(t[i-p[i]] == t[i+p[i]]) p[i]++;
if (i + p[i] > mx) {
mx = i + p[i];
id = i;
}
if (p[i] > resLen) {
resCenter = i;
resLen = p[i];
}
}
return s.substr((resCenter - resLen)/2, resLen-1);
}
顺便提醒大家,构造数组时尽量使用int[]类原生数据类型,会比vector之类的快很多。共勉。