マンチャーアルゴリズムを練習問題付きで詳しく解説

マンチャーアルゴリズム

1 はじめに

  • 回文の部分文字列を計算するために使用され、配列は現在位置を中心として両側に拡張された回文の最長半径を記録するために使用されます。

2. 回文の部分文字列の長さを見つけます

次の 2 つの方法があります。

方法 1:

  • 最適化を行わず、各位置を中心にして両側で一致させる総当りソリューション。
  • この方法には欠陥があり、奇数のみを照合できます。たとえば、中央にabaある 2 つの b の場合は照合できません。abba
  • 解決策は、各文字の途中に特殊文字を追加することです#。どの文字でも問題ありません。文字列内に存在する文字も使用できます。
  • すると文字列abbaは となり#a#b#b#a#、この時両側に展開してもエラーにはなりません

方法 2:

  • 長さを求めるマンチャーアルゴリズム
  • まず元の文字列に特殊文字を追加してマンチャー文字列にします
  • 変数 C、R、および配列 arr を使用して、最長の回文部分文字列の長さを見つけます
  • 変数 C の意味: 最長の回文部分文字列の中心位置
  • 変数 R の意味: 右端の一致が成功した位置 + 1
  • arr 配列の意味: 現在位置の回文の長さ
  • for ループi = 0には、走査から文字列の終わりまでの 4 つの状況があります 。
    • 4 つのケースを理解する前に、重要なポイントを理解する必要があります
    • C が中心位置なので、C が位置する位置の左右に大きな回文領域があり、left...rightCiの位置の後ろに位置するので、iC の位置と対称の位置になることができ、それは得られますi'、つまりこのようにi'...C...i
    • case1:i > R代表者がi不在のためR、暴力的に解決するしかない
    • case2:この時点でが C に従って対応する位置を見つけ、その値を直接取得することi < Rを示します。 C の広い範囲では、 C の左側と C の右側は対称であるため、回文になります。したがって、値 と の値についても同様ですiRii'arr[i']i'iarr[i']arr[i]
    • case3: のときi < R、このとき、 iC に従って対応する位置が見つかります。の範囲が C の回文の範囲内になくなったi'場合、値の範囲内にあるため、この時点で の値を直接取ることはできません。 C の回文の範囲であるため、最長の応答 テキストの長さは C の回文範囲の右側 (R であるため) のみにすることができます。i'arr[i']iiarr[i] = R - i
    • case4:i < Rこのとき、 がiC に従って対応する位置を見つけるとき、 C の左側が C の回文範囲の左側に押されたi'場合、値を直接取得することはできませんが、少なくとも決定された回文の長さは取得できます。 C の右側の回文範囲、つまり R の位置なので、回文の長さは最初から計算できます。後から回文に遭遇すると、長さは増加し続けます。i'arr[i']arr[i]R-i

3. 完全なコード

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

string mancherString(string s) {
    
    
	string str = "#";
	for (int i = 0; i <= s.size(); i++) {
    
    
		str.push_back(s[i]);
		str.push_back('#');
	}
	return str;
}

int main() {
    
    
	string s = "abc1234321ab";
	string str = mancherString(s); // #a#b#c#1#2#3#4#3#2#1#a#b#
	vector<int> pArr(str.size(), 0);
	int C = -1; // 回文串的中心位置
	int R = -1; // 最右的扩成功位置,再下一个位置
	int Max = -1;
	for (int i = 0; i < str.size(); i++) {
    
    
		// i'...C...i
		// 当前位置是i  可以根据C对称的找到i' , 因为C所在的位置左右两侧有一个很大的回文区域left...right范围
		// case1: i'回文串的范围 在C回文串范围left和right内
		// 此时i可以直接取i'的值,因为i也在C的范围内,然后又是对称取的i'

		// case2: i'回文串的范围 在C回文范围left和right外
		// 因为i'的范围已经不在left和right内了,但是i在C的回文范围内,i最大长度也只能到C的right,也就是R
		// 此时直接出答案 i的值为R-i

		// case3: i'回文串的范围 左侧压到了C回文范围的left
		// 也就是说,i的最小长度是可以到C的right,当初C的left-1和right+1不是回文,只是因为这两个值不同
		// 但是有可能i的left-1位置和C的right+1位置相同,所以说得从right+1开始判断是否回文
		pArr[i] = R > i ? min(pArr[C * 2 - i], R - i) : 1; // C * 2 - i 就是 i'的位置
		
		// i - pArr[i]相当于left , i + pArr[i]相当于right
		// i - pArr[i] 左边最开始待匹配的位置 , i + pArr[i] 右边最开始待匹配的位置
		while (i - pArr[i] > -1 && i + pArr[i] < str.size()) {
    
    
			// 暴力扩,如果右侧第一个位置跟左侧第一个位置相等,代表回文长度需要加1,因为又是一个回文
			if (str[i - pArr[i]] == str[i + pArr[i]]) {
    
    
				pArr[i]++;
			}
			else {
    
    
				// 如果中了case1和case2会直接退出循环
				break;
			}
		}
		// 更新C和R
		if (i + pArr[i] > R) {
    
    
			R = i + pArr[i];
			C = i;
		}
		// 记录最大长度
		Max = max(Max, pArr[i]);
	}
	cout << "Ans :" << Max - 1 << endl; // Ans :7
	return 0;
}

4. マンチャーに関する関連トピック

文字列の場合、文字を追加して全体として回文にしたいのですが、追加できるのは元の文字列の末尾のみなので、最後に追加した最も短い文字列を返してください。

元の文字列 A とその長さ n が与えられた場合、追加された文字列を返してください。元の文字列が回文でないことを確認してください。

テスト例:
"ab"、2 が
返される: "a"


タイトルはコアコードモードですが、ACMモードと書きました。

アイデア:
マンチャー アルゴリズムにおける arr 配列の各位置の意味: 現在の位置を中心として取り、両側に拡張し、最長の回文部分文字列の長さ したがって、この問題はマンチャーによって解決され
、質問は、文字列の末尾に文字を追加することです。そのため、arr 配列を見つけるときに、文字列の末尾まで拡張できる位置の右側を見つけ、長さを保存して、ループを終了するだけで済みます。元の文字列で取得した部分文字列を走査し、部分文字列の順序を逆にすると
0位置これ原字符串的长度减去刚刚保存的长度加1が答えです

コード

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

string mancherString(string s) {
    
    
	string str = "#";
	for (int i = 0; i < s.size(); i++) {
    
    
		str.push_back(s[i]);
		str.push_back('#');
	}
	return str;
}

int process(string str) {
    
    
	int C = -1;
	int R = -1;
	int ans = 0;
	vector<int> pArr(str.size());
	for (int i = 0; i != str.size(); i++) {
    
    
		pArr[i] = R > i ? min(pArr[C * 2 - i], R - i) : 1;
		while (i + pArr[i] < str.size() && i - pArr[i] > -1) {
    
    
			if (str[i + pArr[i]] == str[i - pArr[i]]) {
    
    
				pArr[i]++;
			}
			else {
    
    
				break;
			}
		}
		if (i + pArr[i] > R) {
    
    
			C = i;
			R = i + pArr[i];
		}
		if (R == str.size()) {
    
    
			ans = pArr[i];
			break;
		}
	}
	return ans;
}

int main() {
    
    
	string s = "abbbbbaa"; 
	string str = mancherString(s);
	int len = process(str);
	cout << len;
	string ans = s.substr(0, s.size() - len + 1);
	reverse(ans.begin(), ans.end());
	cout << ans;//bbbbba
	return 0;
}

おすすめ記事

KMP アルゴリズムの詳細な説明と練習問題

おすすめ

転載: blog.csdn.net/weixin_44839362/article/details/117135503