Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example 2:
Input: “cbbd”
Output: “bb”
这题很神的hhhh,有很简单的写法,比如暴力枚举所有子串,然后判断该子串是否是回文串,复杂度是 O ( n 3 ) O(n^3) O(n3)。再比如,以每个位置以及每两个元素间的空隙为中心,向两边延申,边延申边判断是否符合回文的条件,复杂度是 O ( n 2 ) O(n^2) O(n2) (sounds promising!我在下面写一下这种写法,年轻人不要上来就搞那么复杂)
两边延申的代码如下:
class Solution {
public:
pair<int,int> check(string s,int a,int b){
int n=s.length();
while(a>=0&&b<n){
if(s[a]!=s[b])break;
a--;b++;
}
return make_pair<int,int>(b-a-1,a+1);
}
string longestPalindrome(string s) {
int len=s.length();
pair<int,int> res=make_pair<int,int>(0,0);
for(int i=0;i<len;i++){
pair<int,int> p1=check(s,i,i);
pair<int,int> p2=check(s,i,i+1);
if(p1.first>res.first)res=p1;
if(p2.first>res.first)res=p2;
}
return s.substr(res.second,res.first);
}
};
再或者用动态规划,令 dp[i][j]
表示区间 [i,j]
是否为回文串,复杂度也为
O
(
n
2
)
O(n^2)
O(n2):
class Solution {
public:
bool dp[1005][1005];
string longestPalindrome(string s) {
int n=s.length();
pair<int,int> res=make_pair<int,int>(0,0);
for(int len=1;len<=n;len++)
for(int i=0;i+len-1<n;i++){
int j=i+len-1;
if(s[i]==s[j]&&(len==1||len==2||dp[i+1][j-1])){
if(len==1||len==2)dp[i][j]=true;
else dp[i][j]=dp[i+1][j-1];
if(len>res.first){
res.first=len;
res.second=i;
}
}
}
return s.substr(res.second,res.first);
}
};
最后是 Manacher 算法,大致思路是首先把一个字符首位以及中间间隔处填充其他字符,比如把 abdhs
填充成 $#a#b#d#h#s#
,设新的串为 t
,定义 p[i]
为以位置 i
为中心的回文串的长度,然后维护一个已知最长的回文串的中心坐标 id
,以及其对应的右边界 maxr
(maxr=id+p[id]
),注意此处的边界是开的,也就是说 t[id-p[id]]!=t[id+p[id]]
,初始化 maxr
为零就行。然后从 1
开始遍历每个位置,根据观察可以发现 p[i]
的值和 i
关于 id
处对称的位置 id-(i-id)=2*id-i
处的值 p[2*id-i]
应该有一些对应关系,其限制条件就是 p[2*id-i]
不能大于 maxr-i
因为大于的部分无法保证依旧对称,因此令 p[i]
的初值为 min(maxr-i,,p[id*2-i])
,此时便利用了之前已经算过的信息。此算法的复杂度为
O
(
n
)
O(n)
O(n) ,因为在每次循环中只有两种情况:没有更新 maxr
,此时说明直接用的 p[id*2-i]
的值,也就是说之前得到的信息完全在范围内,那么再往外的扩展一定是不行的,所以此情况下是
O
(
1
)
O(1)
O(1);另一种情况是更新了 maxr
,由于 maxr
只能向右移,并且 while
处向外推了多少,maxr
就会向右移多少,总的下来 maxr
最多移
O
(
n
)
O(n)
O(n)。综上,总的复杂度是
O
(
n
)
O(n)
O(n)。
另外还需要考虑的是结果的输出,找到最大长度只需要遍历即可,其最大长度应该是 p[i]-1
,因为 p[i]
这个地方是开的,脑补一下= = ,这个直接就是原串中最长回文串的长度,那怎么找在原串中的起始下标?我是这样考虑的,首先很容易得到 t
中最长回文串的中心下标 i
,首先通过 (i-1)/2
将其映射回原串的中心,然后 (i-1)/2-(p[i]-1)/2
即为起始下标。
class Solution {
public:
int p[2005];
string longestPalindrome(string s) {
int n=s.length();
string t="$#";
for(int i=0;i<n;i++){
t+=s[i];
t+="#";
}
int m=t.length();
int maxr=0,id;
for(int i=1;i<m;i++){
p[i]=i<maxr?min(maxr-i,p[id*2-i]):1;
while(t[i-p[i]]==t[i+p[i]])p[i]++;
if(i+p[i]>maxr){
maxr=i+p[i];
id=i;
}
}
int res=0,lo=0;
for(int i=0;i<m;i++)
if(p[i]-1>res){
res=p[i]-1;
lo=(i-1)/2-(p[i]-1)/2;
}
return s.substr(lo,res);
}
};
很优秀:
另:还看到了一个比较神的做法,复杂度分析起来也是 O ( n 2 ) O(n^2) O(n2) ,但是实际上运算量不大:
class Solution {
public:
string longestPalindrome(string s) {
int n=s.length();
if(n==0||n==1)return s;
int maxl=0,lo=0;
for(int i=0;i<n;i++){
int mink=i;
if(maxl>(n-i-1)*2+1)continue;
while(i+1<n&&s[i+1]==s[i])i++;
int maxk=i;
int curl=maxk-mink+1;
mink--;maxk++;
while(mink>=0&&maxk<n&&s[mink]==s[maxk]){
mink--;maxk++;curl+=2;
}
if(curl>maxl)maxl=curl,lo=mink+1;
}
return s.substr(lo,maxl);
}
};
超快的哦