5種解法:
1.最長公共子串
2.暴力法
3.動態規劃
4.中心擴充套件法
5.manacher法
以下記錄大佬題解:
演算法:什麼叫回文串?
如果乙個字串正著讀和反著讀是一樣的,那它就是回文串。
中心擴充套件演算法
我們觀察到回文中心的兩側互為映象。因此,回文可以從它的中心展開,並且只有 2n - 1 個這樣的中心。
你可能會問,為什麼會是 2n - 1 個,而不是 n 個中心?
因為回文的中心要區分單雙。
假如回文的中心為 雙數,例如 abba,那麼可以劃分為 ab bb ba,對於n長度的字串,這樣的劃分有 n-1 種。
假為回文的中心為 單數,例如 abcd, 那麼可以劃分為 a b c d, 對於n長度的字串,這樣的劃分有 n 種。
對於 n 長度的字串,我們其實不知道它的回文串中心倒底是單數還是雙數,所以我們要對這兩種情況都做遍歷,也就是 n+(n-1) = 2n - 1,所以時間複雜度為 o(n)。
當中心確定後,我們要圍繞這個中心來擴充套件回文,那麼最長的回文可能是整個字串,所以時間複雜度為 o(n)。
所以總時間複雜度為 o(n^2)
**如下:
string longestpalindrome(string s)
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++)
}return s.substr(start, end - start + 1);
}int expandaroundcenter(string s, int left, int right)
return r - l - 1;
}manacher(馬拉車) 演算法
前面解法存在以下缺陷:
由於回文串長度的奇偶性造成了不同性質的對稱軸位置,前面解法要對兩種情況分別處理。
很多子串被重複多次訪問,造成較差的時間效率,例如:
字元: a b a b a
位置 : 0 1 2 3 4
當位置為 1 和 2 時,按中心擴充套件法,可以看出左邊的 aba 分別被遍歷了一次。
如果我們能改善重複遍歷的不足,就很有希望能提高演算法的效率。manacher 正是針對這些問題改進演算法。
解決單雙兩次遍歷的問題
首先對字串做乙個預處理,在所有的空隙位置(包括首尾)插入同樣的符號,要求這個符號是不會在原串**現的。這樣會使得所有的串都是奇數長度的,並且回文串的中心不會是雙數,以插入#號為例:
aba ———> #a#b#a#
abba ———> #a#b#b#a#
解決重複訪問的問題
在前面的基礎上,我們認為回文串的中心總是為 單數,我們把乙個回文串中最左或最右位置的字元與其對稱軸的距離稱為回文半徑,用 rl 表示。
用 rl[i] 表示以第 i 個字元為對稱軸的回文串的回文半徑。我們一般對字串從左往右處理,因此這裡定義 rl[i] 為第 i 個字元為對稱軸的回文串的最右乙個字元與字元 i 的距離,如 aba 的 rl[1]=2,即 ba。
對於上面插入分隔符之後的兩個串,可以得到rl陣列:
字元: # a # b # a #
rl : 1 2 1 4 1 2 1
rl-1: 0 1 0 3 0 1 0
位置: 0 1 2 3 4 5 6
字元: # a # b # b # a #
rl : 1 2 1 2 5 2 1 2 1
rl-1: 0 1 0 1 4 1 0 1 0
位置: 0 1 2 3 4 5 6 7 8
rl[i] 的大小總是定義為回文串最右的字元位置-回文串的對稱軸字元位置+1,參看上圖。
上面我們還求了一下 rl[i]-1。通過觀察可以發現,rl[i]-1 的值,正是在原本那個沒有插入過分隔符的串中,以位置 i 為對稱軸的最長回文串的長度(注意,這裡是全串的總長度,不要和 rl 半徑混在一起了)。
於是問題變成了,怎樣 高效地求的rl陣列。基本思路是利用 回文串的對稱性,擴充套件回文串。
我們再引入乙個輔助變數 maxright,表示當前訪問到的所有回文子串,所能觸及的最右乙個字元的位置。另外還要記錄下 maxright 對應的回文串的對稱軸所在的位置,記為 pos,它們的位置關係如下。
我們從左往右地訪問字串來求rl,假設當前訪問到的位置為i,即要求rl[i],在對應上圖,因為我們是從左到右遍歷i, 而pos是遍歷到的所有回文子串中某個對稱軸位置(maxright最大時),所以必然有pos<=i,所以我們更關注的是,i是在maxright的左邊還是右邊。我們分情況來討論。
1)當i在maxright的左邊
可以用下圖來刻畫:
我們知道,圖中兩個紅色塊之間(包括紅色塊)的串是回文。
並且以i為對稱軸的回文串,是與紅色塊間的回文串有所重疊的。
我們找到i關於pos的對稱位置j,這個j對應的rl[j]我們是已經算過的。
根據回文串的對稱性,以i為對稱軸的回文串和以j為對稱軸的回文串,有一部分是相同的。這裡又有兩種細分的情況。
1.1)以j為對稱軸的回文串比較短,短到像下圖這樣
這時我們知道rl[i]至少不會小於rl[j],並且已經知道了部分的以i為中心的回文串,於是可以令rl[i]=rl[j] 為起始半徑。
又因為(j + i) / 2 = pos ==> j = 2*pos - i 得到 rl[i]=rl2*pos - i]。
因此我們以rl[i]=rl2*pos - i]為起始半徑`,繼續往左右兩邊擴充套件,直到左右兩邊字元不同,或者到達邊界。
1.2)以j為對稱軸的回文串很長,超過了maxright在左側的對稱點
這時,我們只能確定,maxright - i 的部分是以i為對稱軸的回文半徑。
因此我們以rl[i] = maxright - i為起始半徑,繼續往左右兩邊擴充套件,直到左右兩邊字元不同,或者到達邊界。
綜上1.1 1.2分析,可以得出:在後面的**中有體現
if (i < maxright)
2)當i在maxright的右邊
遇到這種情況,說明以i為對稱軸的回文串還沒有任何乙個部分被訪問過,於是只能從i的左右兩邊開始嘗試擴充套件了,也就是rl[i]=1。
當左右兩邊字元不同,或者到達字串邊界時停止。然後更新maxright和pos。
1)2) 分析結合的**為:
if (i < maxright)
else
// 嘗試擴充套件rl[i],注意處理邊界
while (i - rl[i] >= 0 // 可以把rl[i]理解為左半徑,即回文串的起始位不能小於0
&& i + rl[i] < len // 同上,即回文串的結束位不能大於總長
&& s1[i - rl[i]] == s1[i + rl[i]]// 回文串特性,左右擴充套件,判斷字串是否相同
)為了得到字串,我們還需要乙個maxrl來記錄最大回文串的回文半徑。maxpos 來記錄maxrl對應的回文串的對稱軸所在的位置。
由前面的分析可以知道, maxrl- 1即為原始最大回文串的長度(注意,這裡是全串的總長度,不要和rl半徑混在一起了)。
原始最大回文串的起始位為(maxpos - maxrl + 1) / 2
**如下:
string longestpalindrome(string s)
// 預處理
string s1;
for (int i = 0; i < len; i++)
s1 += "#";
len = s1.length();
int maxright = 0; // 當前訪問到的所有回文子串,所能觸及的最右乙個字元的位置
int pos = 0; // maxright對應的回文串的對稱軸所在的位置
int maxrl = 0; // 最大回文串的回文半徑
int maxpos = 0; // maxrl對應的回文串的對稱軸所在的位置
int* rl = new int[len]; // rl[i]表示以第i個字元為對稱軸的回文串的回文半徑
memset(rl, 0, len * sizeof(int));
for (int i = 0; i < len; i++)
力扣 最長回文子串
給定乙個字串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。示例 1 輸入 babad 輸出 bab 注意 aba 也是乙個有效答案。我的第一想法是暴力,然後才是中心擴充套件法 雖然知道應該用動態規劃,但是實現不出來 public string longestpalindr...
力扣 最長回文子串 C
給定乙個字串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。輸入 babad 輸出 bab 注意 aba 也是乙個有效答案。輸入 cbbd 輸出 bb include using namespace std define max a,b a b a b define min...
力扣 05最長回文子串
1.暴力法 include 回文數.hpp include include include using namespace std class solution 左邊 右邊 left right return true public string longestpalindrome string s...