owned this note
owned this note
Published
Linked with GitHub
# LPS
Longest Palindromic Substring(最常迴文子字串)
## Manacher's Algorithm
O(n)演算法,以中心拓展法為基礎,利用迴文性質,先求出該點為中心時最小的回文子字串,再利用中心擴展法找到額外的回文子字串
1. 先在字串的每個字元中插入一個特殊符號,包括頭尾
```cpp=
string str="#";
for(int i=0;i<s.size();i++){
str+=s[i];
str+='#';
}
```
2. 每次遍歷都會維護當前最大的子回文
```cpp=
vector<int> p(str.size()); //p[i]=以i為中心點向外擴張的回文半徑
int R=0,C=0; //C=當前最大回文的中心點,R=當前最大回文的右界
for(int i=0;i<str.size();i++){
.
.
.
if(i+p[i]>R){
R=i+p[i];
C=i;
}
}
```
3. 接著遍歷,i如果在最大回文裡,可由鏡射獲得最基本的回文半徑,因為回文性質,在回文內都是對稱的
```cpp=
vector<int> p(str.size());
int R=0,C=0;
for(int i=0;i<str.size();i++){
if(i<R){ //如果i在最大回文裡
int mirror=C-(i-C); //最大回文裡鏡射到的元素編號
p[i]=min(R-i,p[mirror]); //和R-i取min,因為不可越界,越界後無法保證和外面的字串有回文
}else{
p[0]=0;
}
.
.
if(i+p[i]>R){
R=i+p[i];
C=i;
}
}
```
4. 中心拓展法找到額外的回文
```cpp=
vector<int> p(str.size());
int R=0,C=0;
for(int i=0;i<str.size();i++){
if(i<R){
int mirror=C-(i-C);
p[i]=min(R-i,p[mirror]);
}else{
p[0]=0;
}
while(i-1-p[i]>=0 && i+1+p[i]<str.size() && str[i-1-p[i]]==str[i+1+p[i]]){ //中心拓展法,要注意邊界條件
p[i]++;
}
.
.
if(i+p[i]>R){
R=i+p[i];
C=i;
}
}
```
5. 更新最大回文半徑和中心點
```cpp=
vector<int> p(str.size());
int R=0,C=0;
int len=0,center;
for(int i=0;i<str.size();i++){
if(i<R){
int mirror=C-(i-C);
p[i]=min(R-i,p[mirror]);
}else{
p[0]=0;
}
while(i-1-p[i]>=0 && i+1+p[i]<str.size() && str[i-1-p[i]]==str[i+1+p[i]]){
p[i]++;
}
if(p[i]>len){
len=p[i];
center=i;
}
if(i+p[i]>R){
R=i+p[i];
C=i;
}
}
```
6. 最大回文就是str的$\frac{C-len}{2}$~$\frac{C+len}{2}-1$,$-1$是因為要扣掉一個#字號,每個回文前後一定都是#字號
```cpp=#j#8#8#j#
string ans;
ans=s.substr((center-len)/2,len);
```
完整Code
```cpp=
string longestPalindrome(string s) {
string str="#";
for(int i=0;i<s.size();i++){
str+=s[i];
str+='#';
}
int R=0,C=0,len=0,center=0,n=str.size();
vector<int> p(n);
for(int i=0;i<n;i++){
if(i<R){
int mirror=C-(i-C);
p[i]=min(R-i,p[mirror]);
}else{
p[0]=0;
}
while(i-1-p[i]>=0 && i+1+p[i]<n && str[i-1-p[i]]==str[i+1+p[i]]){
p[i]++;
}
if(p[i]>len){
len=p[i];
center=i;
}
if(i+p[i]>R){
R=i+p[i];
C=i;
}
}
string ans;
ans=s.substr((center-len)/2,len);
return ans;
}
```
## Dynamic Programming
利用botton-up來求(bool)dp[l][r]間是否是回文
dp狀態轉移如下:
```cpp=
bool dp[MAXN][MAXN]; //字串s[l, r]是不是回文
//Case 1
if(l==r) dp[l][r]=true;
//Case 2
if(s[l]==s[r] && l+1==r) dp[l][r]=true;
//Case 3
if(s[l]==s[r]) dp[l][r]=dp[l+1][r-1];
//Case 4
if(s[l]!=s[r]) dp[l][r]=false;
```
1. Case 1代表只剩一個字元,回文長度為1
3. Case 2代表只剩兩個字元,且兩個字元相同,回文長度為2
4. Case 3代表兩個不相鄰的字元相同,則dp[l][r]=dp[l+1][r-1]
如果中間不是回文則當前substring也不是回文
如果中間是回文則當前substring是回文,回文長度為r-l+1
4. 如果兩個字元不是回文則當前substring不是回文
<a href="https://leetcode.com/problems/longest-palindromic-substring/">LeeCode : Longest Palindromic Substring</a>
button_up(AC)
```cpp=
string longestPalindrome(string s){
int n=s.size();
vector<vector<bool>> dp(n+1,vector<bool>(n+1,0));
int ans=0,idx;
for(int l=n;l>0;l--){
for(int r=l;r<=n;r++){
if(l==r){
dp[l][r]=true;
}else if(s[l-1]==s[r-1]){
if(r-l==1) dp[l][r]=true;
else dp[l][r]=dp[l+1][r-1];
}
if(dp[l][r] && r-l>=ans){
ans=r-l+1;
idx=l-1;
}
}
}
return s.substr(idx,ans);
}
```
top-down(TLE)
```cpp=
int ans,idx;
bool dp[1000][1000];
bool vis[1000][1000];
bool dpf(int l,int r,string s){
if(l>r) return 0;
if(vis[l][r]) return dp[l][r];
bool result=false;
if(l==r){
result=true;
}else if(s[l-1]==s[r-1]){
if(r-l==1) result=true;
else result=dpf(l+1,r-1,s);
}
if(result && r-l>=ans){
ans=r-l+1;
idx=l-1;
}
dpf(l,r-1,s);
dpf(l+1,r,s);
dp[l][r]=result;
vis[l][r]=true;
return dp[l][r];
}
class Solution {
public:
string longestPalindrome(string s) {
ans=0,idx=0;
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
dpf(1,s.size(),s);
return s.substr(idx,ans);
}
};
```
另一種想法
```cpp=
string longestPalindrome(string s){
int n=s.size();
vector<vector<int>> dp(n+1,vector<int>(n+1,0));
int ans=0,idx;
for(int l=n;l>0;l--){
for(int r=l;r<=n;r++){
if(l==r) dp[l][r] = 1;
else if(s[l-1]==s[r-1] && l+1==r) dp[l][r] = 2;
else if(s[l-1]==s[r-1]) dp[l][r] = (dp[l+1][r-1]!=0?dp[l+1][r-1]+2:0);
else dp[l][r] = 0;
if(dp[l][r]>ans){
ans=dp[l][r];
idx=l-1;
}
}
}
return s.substr(idx,ans);
}
```