これは、喜びと喜びの最初の本である343、最初の更新367ピアンオリジナル
Manacherのアルゴリズム、中国のアルゴリズムと呼ばれる馬車車両は、Manacherという名前のアルゴリズム者が問題を解決するために、1975年に提案されている最長の回文の部分文字列を見つけることです、魔法は、洗練されたアルゴリズムの時間複雑ですO(N)
さんは、このアルゴリズムの詳細なアイデアを見てみましょう。
01起源アルゴリズム
問題最長の回文構造部分文字列を解決すると、現在の文字が中央の一般的な考え方で、左右の拡張パリンドロームみるとこれが、このソリューションの時間はO(N ^ 2)の複雑さである、それはすることができます時間の複雑さと、それを減らしますか?線形ますか?馬車車のアルゴリズムは、この問題に対する最適なソリューションです。
02前処理
パリンドロームその長ストリング点、奇数回文(奇数である長さ)もパリンドロームに分けることができる(その長さは、偶数である)、一般パリンドローム2例を見つける必要があり、アルゴリズム描かカートこの手順を簡素化するために、元の文字列が奇数回文に文字列ように、各文字の左右には、(確かに文字の元の文字列には存在しない)特殊文字を追加している時に、処理されました。例えば:
元の文字列:アバ、4の長さ
前処理後:##のB#Bが# #、 長さ9
元の文字列:ABA、3の長さは
前処理:## Bが## 、 7の長さ
03は最も長いサブパリンドロームの長さを算出し、
文字列は"cabbaf"
、例えば、新たな文字列は前処理"#c#a#b#b#a#f#"
文字配列ARRに、補助配列の定義int[] p
、p
の長さarr
に等しい長さは、p[i]
として表さarr[i]
中心最長文字回文半径、p[i]=1
それだけ示すarr[i]
文字自体バックテキスト文字列。
i 0 1 2 3 4 5 6 7 8 9 10 11 12
arr[i] # c # a # b # b # a # f #
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
半径と最長の回文元の文字列の間の関係を分割するよりも、見てみましょう。上記の例では、最長のサブストリングはパリンドロームであり"#a#b#b#a#"
、それはARR [6]を中心として、半径は元の文字列を表す、5であり"abba"
、及び"abba"
4の長さは、文字列、5から1を減算することにより得られます"cabbaf"
最長の回文構造部分文字列は、その後、我々は半径と最長の回文最長の回文構造部分文字列の長さとの関係を取得することができませんか?
私たちのようなより多くのいくつかの例、見てみましょう"aba"
、変換された"#a#b#a#"
文字に'b'
-centric回文、半径4、3,3マイナス1は、元の文字列のパリンドロームサブの最長の長さです。
別の例として"effe"
、変換は、"#e#f#f#e#"
最も「#」の中央にパリンドロームの中心、半径5であり、4,4、マイナス1を与えるためには、元の文字列のパリンドロームサブストリングの最大長です。
したがって、我々は最終的に最大半径と最大パリンドロームパリンドロームサブストリングの長さとの関係を得ます:int maxLength = p[i]-1
。maxLength
これは、最長サブパリンドロームの長さを表しています。
04は、インデックスパリンドロームを開始する最長の部分文字列を算出し、
最長の回文の部分文字列の長さを知って、我々はまた、最長の回文サブストリングの傍受を完了するように、開始インデックス値を知っておく必要があります。
ストリングを継続する第三のステップにおいて"cabbaf"
、例えば、p[6]=5
それは、1を与えるために最大半径5(P [I])を減算することにより、図6(I)と最長半径、1最長サブ回文「アバ」であることを起こります開始インデックス。
私たちは奇妙な回文の例を見てみましょう。例えば"aba"
、変換後"#a#b#a#"
、P [3]私は4は、I-1、配列添字境界を引いた、3であり、最大半径が4である、4 =。
でも回文の場合、私は奇妙な回文ながら、半径を満たすためにカットアップが、それは範囲外の添字ます、我々は国境を越えたインデックスの問題を解決するために、変換後の文字列の前に文字を追加する必要があり、それはすることはできません'#'
、その後、追加し'$'
ての性格を、しかし、文字ではなく、文字列の奇数長さ、文字の上に追加した後、尾部に定期的なプラスになるだけではなく、例えば'@'
、この文字列の長さはまだ奇数で、フロントに会います第三の部分の条件。
カドガンは、文字の後に、奇数回文は通常の減算を行うことができ、さらにはそれを回文?
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13
arr[i] $ # c # a # b # b # a # f #
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
充填文字'$'
の後に、P [7] = 2 7-5最大半径を減算することにより、私を5 =、その結果が理想的であるべきである一方が起きることができるように、それは、2で除算されます。私が手に奇数回文「ABA」、マイナス最長半径は、2または0で除算し、0であるクロスボーダーインデックスの問題を完璧に解決することができます。
結論:パリンドローム最長の部分文字列の開始インデックスint index = (i - p[i])/2
。
コンピューティングの05 Pアレイ
第三工程および第四のステップでは、我々は、ストレージは最長の回文は半径をサブストリングされたキーのオブジェクトpの配列を使用していた、そしてそれはどのようにについて来たのですか?
あるいは、上記の例では、表情と合わせ、
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
arr[i] $ # c # a # b # b # a # f # @
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
二つの変数MXとIDを設定し、IDパリンドロームサブストリングは、すべて、右端位置パリンドロームサブストリングの中心点に拡張することができる、MXは、文字列の右端の位置が回文に拡張することが可能です。
iが7に等しい場合、IDは7、P [ID] = 5、中央7の位置におけるパリンドロームサブストリングと同じである、パリンドローム配列は、サブ右境界位置12です。
iが12に等しい場合、IDは、P [ID] = 2 12に等しい、中央にパリンドロームストリング12の位置は、サブパリンドローム配列の右側の境界線は、位置14です。
このことから、我々は権利とパリンドロームサブ半径との関係を描くことができますmx = p[id]+id
。
文字列が中心対称パリンドロームであるので、あなたが中心としてIへパリンドローム配列のサブ位置場合、IDの中心点を知っている、中央にパリンドロームサブIDを含む、すなわちmx > i
、それは確かに他に存在するであろうJパリンドロームサブセンター、iはサブパリンドロームストリングの中央、すなわち、等しいと対称的であるp[j] = p[i]
、すなわち、ID i及びjは対称的に集中しているi+j=2*id
あなたは、iの値を知っていれば、次に、j = 2*id - i
。
I-中心部分文字列との回文がある場合しかし、我々は別のケースを考慮する必要があり、まだ戻って>私が、右ボーダーMXの上にサブストリングI-中心回文、IさんにこのMXにMXを持っていますテキストにおけるサブストリング、及びこの時、中心又は等しいj個のサブストリングへパリンドローム対称の他端p[i] = mx - i
、p[j] = [pi]
などのための
したがって、mx > i
下p[i] = Math.min(p[2*id - i], mx - i)
状況、。
私はMXよりも大きい場合にも、つまり、MXのサブストリングの境界線の後ろに、文字がまだ計算を比較する必要があります。
public static String Manacher(String s) {
if (s.length() < 2) {
return s;
}
// 第一步:预处理,将原字符串转换为新字符串
String t = "$";
for (int i=0; i<s.length(); i++) {
t += "#" + s.charAt(i);
}
// 尾部再加上字符@,变为奇数长度字符串
t += "#@";
// 第二步:计算数组p、起始索引、最长回文半径
int n = t.length();
// p数组
int[] p = new int[n];
int id = 0, mx = 0;
// 最长回文子串的长度
int maxLength = -1;
// 最长回文子串的中心位置索引
int index = 0;
for (int j=1; j<n-1; j++) {
// 参看前文第五部分
p[j] = mx > j ? Math.min(p[2*id-j], mx-j) : 1;
// 向左右两边延伸,扩展右边界
while (t.charAt(j+p[j]) == t.charAt(j-p[j])) {
p[j]++;
}
// 如果回文子串的右边界超过了mx,则需要更新mx和id的值
if (mx < p[j] + j) {
mx = p[j] + j;
id = j;
}
// 如果回文子串的长度大于maxLength,则更新maxLength和index的值
if (maxLength < p[j] - 1) {
// 参看前文第三部分
maxLength = p[j] - 1;
index = j;
}
}
// 第三步:截取字符串,输出结果
// 起始索引的计算参看前文第四部分
int start = (index-maxLength)/2;
return s.substring(start, start + maxLength);
}
06まとめ
最長の回文構造部分文字列の複雑さを解決するための馬車車のアルゴリズムはに縮小されO(N)
、空間の一部を犠牲にしているが、その空間的な複雑さをO(N)
、しかし、アルゴリズムのトリックは、学習とリファレンスの価値があります。
テーマ別アルゴリズムが連続しているより半年以上日間、特集記事のアルゴリズム211件の +記事、公衆番号]ダイアログボックスの返信[ データ構造とアルゴリズム ]、[ アルゴリズム ]、[ データ構造の記事コレクションのシリーズを得るために、キーワードのいずれか] 。
それはあなたが何か良い解決策のアイデア、提案やその他の問題がある場合、あなたは格好良い、メッセージ転送およびサポートが私の最大の報酬は、以下のコメントを交換することができ、すべてです!