您的位置:首页 > 其它

LeetCode 5: Longest Palindromic Substring

2015-10-18 15:35 351 查看

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.

解题思路

思路一
暴力(Brute Force)穷举
,时间复杂度O(N3)。既枚举字符串的每一个子串,判断是否是回文子串。返回最长的那一个回文字串即可。代码如下:

class Solution {
private:
// 判断 s[start ... end] 是否是回文串
bool isPalindromicSubstring(string& s, int start, int end) {
int k = 0;
while(start + k < end - k) {
if (s[start + k] != s[end - k]) {
return false;
}
k++;
}
return true;
}

public:
string longestPalindrome(string s) {
string result;
int maxlen = 0;
for (int i = 0; i < s.length(); ++i) {
for (int j = i; j < s.length(); ++j) {
// 枚举字符串的每一个子串,判断是否是回文子串
if (isPalindromicSubstring(s, i, j)) {
if (maxlen < j - i + 1) {
maxlen = j - i + 1;
result = s.substr(i, j + 1);
}
}
}
}
return result;
}
};


思路二:使用
动态规划(DP,Dynamic Programming)
对暴力穷举法进行改进,可以把时间复杂度降到O(N2),但是空间复杂度也是O(N2),诀窍就是避免重复计算(即重复检测同一子串)。考虑字符串“ababa”,如果我们已经检测过“bab”是回文,那么只需判断最左边和最右边的两个字符(即两个a)是否相同即可判定“ababa”是否是回文串了。首先,定义状态转移方程:

Define P[ i, j ] ← true iff the substring Si … Sj is a palindrome, otherwise false.

P[ i, j ] ← ( P[ i+1, j-1 ] and Si = Sj ) ,显然,如果一个子串是回文串,并且如果从它的左右两侧分别向外扩展的一位也相等,那么这个子串就可以从左右两侧分别向外扩展一位。

其中的base case是:P[ i, i ] ← true,P[ i, i+1 ] ← ( Si = Si+1 )

代码如下:

class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
int longestBegin = 0;
int maxLen = 1;
// 二维数组P[i,j]用以表示Si…Sj是否是回文
bool P[1000][1000] = { false };

/*
* 初始状态:
* P[ i, i ] ← true
* P[ i, i+1 ] ← ( Si = Si+1 )
*/
for (int i = 0; i < len; ++i) {
P[i][i] = true;
if ((i < len - 1) && s[i] == s[i+1]) {
P[i][i+1] = true;
longestBegin = i;
maxLen = 2;
}
}

/*
* P[ i, j ] ← ( P[ i+1, j-1 ] and Si = Sj )
* 如果一个子串是回文串,并且如果从它的左右两侧分别向外扩展的一位也相等,
* 那么这个子串就可以从左右两侧分别向外扩展一位。因此,应按子串的长度来
* 填充P[][]数组。
*/
for (int sub_len = 3; sub_len <= len; ++sub_len) {
for (int i = 0; i < len - sub_len + 1; ++i) {
int j = i + sub_len - 1;
if (s[i] == s[j] && P[i+1][j-1]) {
P[i][j] = true;
longestBegin = i;
maxLen = sub_len;
}
}
}

return s.substr(longestBegin, maxLen);
}
};


思路三:回文的特点就是中心对称,若子串S[3,7]不是回文串,则S[2,8]不可能是回文串。因此我们可以
枚举子串的中心位置
,然后再从小到大枚举这个子串的长度,一旦发现已经不是一个回文子串了,就可以继续尝试下一个中心位置。注意对于有N个字符的字符串S,有2*N-1个中心,因为两个字符之间的空档也可以是一个中心。例如“abba”的两个b中间就是一个中心。该算法的时间复杂度为O(N2),空间复杂度为O(1)。代码如下:

class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
int longestBegin = 0;
int maxLen = 1;

// 枚举2*N-1个中心位置
for (int i = 0; i < 2 * len - 1; ++i) {
// 判断是以S[l]一个字符为中心,还是以S[l,l+1]两个字符为中心
int l = i >> 1;
int r = (i & 1) ? l + 1 : l;

// 从中心位置向外扩展
while (l >= 0 && r <= len - 1 && s[l] == s[r]) {
l--;
r++;
}

// 更新结果
if (r - l - 1 > maxLen) {
longestBegin = l + 1;
maxLen = r - l - 1;
}
}

return s.substr(longestBegin, maxLen);
}
};


思路四
Manacher算法
,时间复杂度为O(N)。

在任意两个相邻的字符之间插入一个特殊字符(如‘#’),这样原字符串中长度为偶数或奇数的回文子串在新字符串中都变为长度为奇数的回文子串。如:’aba’ => ‘#a#b#a#’,’abba’ => ‘#a#b#b#a#’。

设S = “abaaba”, 则经过处理后得到字符串T = “#a#b#a#a#b#a#”。为了找到最长回文子串,需要以每个Ti为中心进行扩展形成回文子串Ti-d … Ti … Ti+d,其中d为以Ti为中心的回文串的长度(不包括特殊字符)。我们把各个以Ti为中心的回文长度存储到数组P中,那么当我们求出所有的P[i],取其中最大值就能找到最长回文子串了。如:

T#a#b#a#a#b#a#
P0103016103010
显然最长子串就是以P[6]为中心的”abaaba”。

如何在O(N)的时间内计算出数组P[]呢?
下面以S=”babcbabcbaccba”为例。如下图所示,假设已经算出了一部分P[]的值,且当前找到的最靠右的回文子串的中心位置、最左端和最右端分别用C、L和R表示。那么如何高效地计算P[i]呢?



我们先看一下i围绕C的对称点i’(此时i’=9)。



据上图所示,很明显P[i]=P[i’]=1。这是因为i和i’围绕C对称。同理,P[12]=P[10]=0,P[14]=P[8]=0。



现在再看i=15处。此时P[15]=P[7]=7?错了,你逐个字符检测一下会发现此时P[15]应该是5。

为什么此时规则变了?



如上图所示,两条绿色实线划定的范围必定是对称的,两条绿色虚线划定的范围必定也是对称的。此时请注意P[i’]=7,超过了左边界L。超出的部分就不对称了。
此时我们只知道P[i]>=5,至于以P[i]还能否扩展,只有通过逐个字符检测才能判定了
。在此例中,T[21] != T[9],所以P[15]=5。

总结一下上述分析过程,我们需要处理两种情况:

1. 若 P[ i’ ] ≤ R – i,则有 P[ i ] ← P[ i’ ];

2. 若 P[ i ] ≥ P[ i’ ],此时需要依次比较T[i + k + R] == T[i - k - R](k = 1, 2, …)确定P[i]的最终值。

注意,若以T[i]为中心的回文串的最右端超过了R,需要更新C,L和R的值。

class Solution {
private:
// Transform S into T.
// For example, S = "abba", T = "^#a#b#b#a#$".
// ^ and $ signs are sentinels appended to each end to avoid bounds checking
string preProcess(string s) {
int len = s.length();
if (len == 0) return "^$";
string ret = "^";
for (int i = 0; i < len; i++)
ret += "#" + s.substr(i, 1);
ret += "#$";
return ret;
}
public:
string longestPalindrome(string s) {
// 预处理:插入特殊字符,使原字符串中长度为偶数或奇数的回文子串在新字符串中都变为长度为奇数的回文子串
string T = preProcess(s);

int len = T.length();
// P[i]表示以Ti为中心的回文串的长度
int *P = new int[len];

int C = 0, R = 0; // 表示当前找到的最靠右的回文子串的中心位置和最右端位置的下标
for (int i = 1; i < len - 1; i++) {
int i_mirror = 2*C-i; // i关于C的对称点

/* 若i > R,则只能从T[i]开始匹配,P[i] = 0;否则:
* if P[ i’ ] ≤ R – i,
* then P[ i ] ← P[ i’ ]
* else P[ i ] ≥ P[ i’ ]
*/
P[i] = (R > i) ? min(R-i, P[i_mirror]) : 0;

// Attempt to expand palindrome centered at T[i]
while (T[i + 1 + P[i]] == T[i - 1 - P[i]])
P[i]++;

// If palindrome centered at i expand past R,
// adjust center based on expanded palindrome.
if (i + P[i] > R) {
C = i;
R = i + P[i];
}
}

// Find the maximum element in P.
int maxLen = 0;
int centerIndex = 0;
for (int i = 1; i < len - 1; i++) {
if (P[i] > maxLen) {
maxLen = P[i];
centerIndex = i;
}
}
delete[] P;

return s.substr((centerIndex - 1 - maxLen)/2, maxLen);
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode