Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example:
Input: “cbbd”
Output: “bb”
思路:
1. 简单直接的做法:以每个字符为中心,往两侧扩展找最长的palindromic substring,然后取最大值!复杂度worst case O(n^2)
2. 最快的方法o(n)。manacher’s algorithm。o(n)的算法是由于减少了大量重复不必要的计算。算法本身有点复杂。试举例说明:
Given string:
babababcbaccba
pre-processing:每个符号两边插入一个特殊符号,这样找palindrome就把奇对称和偶对称统一成一种情况了。
0123456789 10 11 13 15 17
#b#a#b#a#b # a # b # c # b#a#c#c#b#a#
从左往右遍历,把每个字符对应的最长的palindrome的长度保存下来。即:空间是o(n)。如上图,在i=5时的最长的palindrome是#b#a#b#a#b,说明以5为中心,两侧对称,这里可以推论一个性质:
关于某个点对称的两个位置的环境相似,即:有有相同的parlindrome,所以长度相同。
所以,i=7和i=3是关于i=5对称,所以i=7的parlindrome和i=3一样,都是:#b#a#b#。注意,能应用这个推论的前提是,i=7和i=3这两个点都在i=5为中心的最长parlindrome的范围之内,即:[0,9].为了用编程或数学语言描述这个parlindrome,需要用两个参数:一种是直接用这个parlindrome的开头和结尾位置;一种是用该parlindrome的center位置和最right的点的位置。这里选用后者!
以上只是一般的情况,在极限的情况下,由于添加了新的界限,需要相应的调整parlindrome的长度。
A. 当right-i < p[mirror],说明此时p[i]最多只能等于right-i, 因为right是现在parlindrome的边界。这里用了减法,需要用right-i>0对前面的条件做保护。
B. 一旦p[i]根据上面的条件确定了,只能说明这个p[i]是在当前的center和right两个参数限定的parlindrome里的sub parlindrome的长度,不一定代表在i处的最长的parlindrome,因为一旦允许跨越这个限定就可能更长。所以,必须比较判定while(T[i+1+p[i]]==T[i-1-p[i]]),如果成立,继续expand p[i]。
现在总结一下整个流程:
1. 在每个位置i处,根据当前的center和right限定的parlindrome,由i关于center对称的i_mirror位置的p[i] (parlindrome长度)
2. 初步得到p[i],还需要进一步检查是否能超出当前center和right限定的parlindrome的范围而扩展以i为中心的parlindrome。
3. 判断是否需要更新center和right的值,如果i+p[i]>right的话
4. 更新最长的parlindrome的center和right
讨论:
1.为什么这个看起来复杂的方法仍然是o(n)? 是因为每个坐标处只是扩展的时候参与运算一次,所以每个点使用一次。
//方法1:O(n)
class Solution {
public:
string preprocess(string&s){
string t;
for(auto k:s)
t=t+'#'+k;
t+='#';
return t;
}
//Preprocess另一种快速方法:比上面快,是因为在初始化的时候就把所有元素赋值'#',然后把偶数位置替换成字母。
string preprocess(string&s){
int len=2*s.size()+1;
string t(len,'#');
int m=0;
for(int i=1;i<len;i+=2)
t[i]=s[m++];
return t;
}
string longestPalindrome(string s) {
//
string T=preprocess(s);
int n=T.size();
vector<int> P(n);
int center=0,right=0;//初始化parlindrome为空
int mxLen=0,idx=0;
for(int i=0;i<n;i++){
int mirror=2*center-i;//找到对称的位置
//在当前限定的parlindrome找i为中心的parlindrome的长度
P[i]=right>i?min(right-i,P[mirror]):0;
while(i+1+P[i]<n&&i-1-P[i]>=0&&T[i+1+P[i]]==T[i-1-P[i]])
P[i]++;//扩展p[i]
if(i+P[i]>right){//更新center和right
center=i;
right=i+P[i];
}
if(P[i]>mxLen){
mxLen=P[i];
idx=i;
}
}
return s.substr((idx-mxLen)/2,mxLen);
}
};
//方法2:o(n^2),奇对称和偶对称都考虑。
class Solution {
public:
void helper(string&s,int left, int right,int mxLen, int start){
while(left>=0&&right<s.size()&&s[left]==s[right])
left--;
right++;
if(mxLen<right-left-1)//
{
mxLen=right-left-1;
start=left+1;
}
}
string longestPalindrome(string s) {
int mxLen=1,start=0;
for(int i=0;i<s.size()-1;i++){
helper(s,i,i,mxLen,start);
helper(s,i,i+1,mxLen,start);
}
return s.substr(start,mxLen);
}
};