https://leetcode.com/problems/longest-palindromic-substring
求解最长公共子串问题
设 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1表示从 i i i到 j j j的字符串是一段回文字符串,反之若 d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0表示从 i i i到 j j j的字符串非回文字符串,那么有:
d p [ i ] [ j ] = { 1 d p [ i + 1 ] [ j − 1 ] = = 1 a n d s [ i ] = = s [ j ] 0 e l s e dp[i][j]=\left\{ \begin{aligned} 1 &\ dp[i+1][j-1]==1 \ and \ s[i]==s[j] \\ 0 & \ else \\ \end{aligned} \right. dp[i][j]={10 dp[i+1][j−1]==1 and s[i]==s[j] else
由于求解区间 [ i , j ] [i,j] [i,j]需要用到内部区间 [ i + 1 , j − 1 ] [i+1,j-1] [i+1,j−1]的信息,故直接从0到s.size()顺序迭代更新 i i i, j j j不可取,所以我们需要改变更新方式。
这里采用的是子串的长度和区间的左端点相结合更新的方式,先预处理子串长度为1和2的情况,然后按照上述公式更新即可。时间复杂度 o ( n 2 ) o(n^2) o(n2)。
class Solution {
public:
int dp[1010][1010];
string longestPalindrome(string s) {
memset(dp,0,sizeof(dp));
for(int i=0;i<s.size();i++){
dp[i][i]=1;
if(i+1<s.size()&&s[i]==s[i+1]) dp[i][i+1]=1;
}
for(int L=3;L<=s.size();L++){
for(int i=0;i+L-1<s.size();i++){
int j=i+L-1;
if(s[i]==s[j]&&dp[i+1][j-1]==1)
dp[i][j]=1;
}
}
for(int L=s.size();L>=1;L--){
for(int i=0;i+L-1<s.size();i++){
int j=i+L-1;
if(dp[i][j]){
return s.substr(i,L);
}
}
}
return s;
}
};
首先预处理,在字符串的首末和中间都加上分隔符号#,保证输入的子串长度是奇数。
具体原因见https://segmentfault.com/a/1190000003914228
利用 m x mx mx表示已经访问的所有回文子串中最右边的位置,
利用 p o s pos pos表示这个子串的回文中心,即有 m x = p o s + p [ p o s ] mx=pos+p[pos] mx=pos+p[pos], p [ p o s ] p[pos] p[pos]表示以 p o s pos pos为回文中心的字符串对应的回文半径,
其次求解字符串中第 i i i个位置对应的回文半径 p [ i ] p[i] p[i]
在求解和更新 p [ i ] p[i] p[i]时,需要注意:
利用 i i i关于回文中心点 p o s pos pos的对称点的信息来简化计算的复杂度,不如设该点为 j j j,那么有:当 i i i在 p o s pos pos和 m x mx mx中间时:
i − p o s = p o s − j i-pos=pos-j i−pos=pos−j
即 j = 2 ∗ p o s − i j=2*pos-i j=2∗pos−i
此时 p [ i ] p[i] p[i]的信息可以更新为 p [ i ] = m i n ( p [ j ] , m x − i ) p[i]=min(p[j],mx-i) p[i]=min(p[j],mx−i)
若 i i i在 m x mx mx右边,即i超出了 p [ p o s ] p[pos] p[pos]对应的回文半径,这时候以i为中心的回文字符串就是其本身,所以 p [ i ] = 1 p[i]=1 p[i]=1
由于 i i i和 j j j关于 p o s pos pos对称,而 p [ i ] p[i] p[i]表示以 i i i为中心的子串的回文半径,显然仅靠 p [ j ] p[j] p[j]的信息是不够的,因为如果 i + p [ i ] > m x i+p[i]>mx i+p[i]>mx后,很可能出现 s [ i + p [ i ] ] = = s [ i − p [ i ] ] s[i+p[i]]==s[i-p[i]] s[i+p[i]]==s[i−p[i]],所以需要进一步更新 p [ i ] p[i] p[i]
回文半径 p [ i ] − 1 p[i]-1 p[i]−1中最大的值对应整个字符串最大的回文子串长度。
总体时间复杂度 o ( n ) o(n) o(n)。
class Solution {
public:
string longestPalindrome(string s) {
if(s.size()<=1) return s;
string t="",q="";
for(int i=0;i<s.size();i++){
t+="#";
t+=s[i];
}
t+="#";
vector<int> ans=solve(t);
int maxv=-1,ind=-1;
for(int i=0;i<ans.size();i++){
if(ans[i]>maxv){
maxv=ans[i];
ind=i;
}
}
for(int i=ind-ans[ind]+1;i<=ind+ans[ind]-1;i++){
if(t[i]!='#') q+=t[i];
}
return q;
}
vector<int> solve(string t){
int pos=0,mx=0;
int len=t.size();
vector<int> p(len);
for(int i=0;i<len;i++){
if(i<mx) p[i]=min(p[2*pos-i],mx-i);//j=2*pos-i
else p[i]=1;
while(i-p[i]>=0&& i+p[i]<len && t[i+p[i]]==t[i-p[i]])//更新p[i]
p[i]++;
if(mx<i+p[i]-1){//更新最右边回文串对应的回文中心pos和右边界mx
pos=i;
mx=i+p[i]-1;
}
}
return p;
}
};