《程式設計之法》1 6最長回文子串

2021-07-15 02:17:54 字數 3854 閱讀 6491

題目描述:給定乙個字串,求它的最長回文子串(子串是連續的)的長度

解法一:列舉法

外面的雙層for迴圈確定連續子串頭尾,加上裡面的乙個for迴圈來判斷該子串是否為回文串,此外用乙個max變數(初始值為1)動態更新儲存最長子回文串的長度,時間複雜度為o(n^3),空間複雜度為o(1)。

#include #include using namespace std;

bool ispalindrome(string &str, int start, int end)

return true;

}int longestpalindrome(string &str)

} return maxlen;

}int main()

return 0;

}

解法二:中心擴充套件法乙個for迴圈將字串中的每個元素作為中心點,並對該中心點是奇數回文串中心還是偶數回文串中心分開寫程式,在該中心點位置上進行擴充套件,依次比較兩邊的對應元素是否相等,時間複雜度為o(n)*o(n/2+n/2)=o(n^2),程式**如下:

#include #include using namespace std;

int longestpalindrome(string &str)

if(maxlen < 2*(j-1) + 1) //注意跳出的j的取值,如asdsf,j=2時跳出,而最長回文子串為:2*(j-1)

maxlen = 2*(j-1) + 1;

//該點為偶數數字符數的回文串中心

for(j = 0; i-j>=0 && i+j+1 <= str.size()-1; ++j)

if(maxlen < 2*(j-1) + 2)

maxlen = 2*(j-1) + 2;

} return maxlen;

}int main()

return 0;

}

解法三:manacher演算法步驟如下:

a,像下面這樣對字串12212321進行插字元

s[i]:*#1#2#2#1#2#3#2#1#

p[i]: 12125214121612121

即先在原字串中每個字元左右插字元#,最後在最前面插字元*。p[i]是以該字元為中心的最長回文子串的右半邊長度(含中心本身)。插入*可以更好的處理越界問題,字元間插入#是為了使所有回文子串都變為奇數,這樣無需像中心擴充套件法那樣還要考慮偶數的問題。

b,接下來是如何求p[i]

這就像動態規劃一樣,先求p[0],再求p[1],再由p[0]和p[1]求p[2],...,再由p[0]~p[i-1]求p[i],...。我們以前用到動態規劃時,如求兩字串的最長公共子串,根據dp[i-1][j-1],dp[i][j-1],dp[i-1][j]來求dp[i][j],故我們需要確定p[i]的**。而在manacher演算法中p[i]的**有兩種,一是若p[i]關於id(下一句解釋id含義)與p[j]對稱,那麼p[i]可由p[j]求出,二是無法使用前面的p[j]時,直接依次比較s[i]兩邊的元素是否相等。

由於之前已經知道以s[1]~s[i-1]為中心的最大回文串長,此時定義兩個變數mx和id,mx指s[1]~s[i-1]為中心的所有最長回文子串中能延伸到的最右端的位置,且是以s[id]為中心的,故mx = id + p[id]。故mx為以id為中心的回文子串右半段的下乙個位置(不在回文串內)。

由此時,分兩種大情況:

1,若mx<=i,此時無法使用s[id]為中心來求得s[i],只能以它為中心依次判斷兩邊元素是否相等。**將p[i]當成i,這樣寫,p[i]=1;while(s[i+p[i] == s[i-p[i]) ++p[i];

2,若mx>i,以以下三種情況進行分析:

a, 如下圖,s[2*id-i]的回文子串完全在s[id]的回文子串範圍時,則根據對稱性,p[i] = p[2*id-i]。

p[i]不可能比這大或比這小,假設p[i]比p[2*id-i]大,如多出如下的1和2部分,由於該範圍是關於id對稱的,最後也會得出p[2*id-i]應該變大,多出3和4部分,矛盾;若假設比它小也會產生相似的矛盾。故p[i] = p[2*id-i]。

b,如下圖,s[2*id-i]的回文子串的左半邊等於id的左側範圍的最左側位置,而下面黑色部分1與紅色部分2不同,兩個紅色部分2,3對稱相等,而藍色部分4有可能與紅色部分3相同,故先令p[i]=mx-i; 再while(s[i+p[i] == s[i-p[i]) ++p[i];

c,如下圖,s[2*id-i]的回文子串的左半邊超過id的左側範圍,故此時p[i]=mx-i。不可能更長了,假設更長,及下面的部分1和部分2相等,而部分2和部分3關於id對稱相等,而部分3和部分4關於2*id-i對稱相等,故部分1和部分4相等,進而推出id的範圍超過了綠色範圍,矛盾。

對於2中的情況a和情況c,令p[i] = p[2*id-i]後, 再執行while(s[i+p[i] == s[i-p[i]) ++p[i];也不會對結果有什麼影響,因為肯定不滿足條件而無法進入while中處理,s[i]依然等於s[2*id-i]。

故核心**為:

if(mx > i) p[i] = min(p[2*id-i], mx-i);

else p[i] = 1;

while(s[i+p[i] == s[i-p[i]) ++p[i];

c,接下來是重新放置id的位置,只需比較i和舊的id誰能向右邊延伸最遠。

完整演算法**如下:

#include #include #define max 110

using namespace std;

int p[2*max + 2];

int manacher(char *s)

s[0] = '$';

s[2*len + 1] = '*';

s[2*len + 2] = '\0';//最後需要加'\0',避免越界

int id = 0, maxlen = 0;

p[0] = 1;

for(i = 1; i <= 2*len+1; ++i)

return maxlen-1;

}int main()

return 0;

}

總結:manacher演算法的時間複雜度一般為o(n),但我認為它的時間複雜度應該比o(n)要大。該演算法和中心擴充套件法差不多,依次從下標1開始從小到大來分別計算以該點為中心的最長回文子串,每次遍歷時,如果可以利用到s[id*2-i]就能省去p[id*2-i]次比較,如果不能利用則還是需要以該中心點依次比較它兩邊的元素。

舉一反三:

輸出最長回文子串:將乙個很長的字串分割成一段一段的子串,要求子串都是回文串,且每次輸出最長的回文串。如輸入"habbafgh",輸出"h","abba","f","g","h"。

可以使用中心擴充套件法將所有回文子串輸出,可能考慮到輸出不能重複的問題,可設定vecter來對每次輸出時進行比較。

也可用曼徹斯特演算法根據得到的p將所有的子回文串輸出,設定vector來解決輸出重複問題。

程式設計題 最長回文子串

對於乙個字串,請設計乙個高效演算法,計算其中最長回文子串的長度。給定字串a以及它的長度n,請返回最長回文子串的長度。測試樣例 abc1234321ab 12返回 7 解題思路 法一 中心擴散法 時間複雜度o n 2 空間複雜度o n 遍歷每個字元,以該字元為中心,向前 後擴散,直到不滿足回文時停下。...

最長回文子串 最長回文子串行

1.最長回文子串行 可以不連續 include include include include using namespace std 遞迴方法,求解最長回文子串行 intlps char str,int i,int j intmain include include include using n...

最長回文子串

描述 輸入乙個字串,求出其中最長的回文子串。子串的含義是 在原串連續出現的字串片段。回文的含義是 正著看和倒著看是相同的,如abba和abbebba。在判斷是要求忽略所有的標點和空格,且忽略大小寫,但輸出時按原樣輸出 首尾不要輸出多餘的字串 輸入字串長度大於等於1小於等於5000,且單獨佔一行 如果...