Javaで回文である部分文字列を見つける
1. 概要
このクイックチュートリアルでは、finding all substrings within a given string that are palindromesへのさまざまなアプローチについて説明します。 また、各アプローチの時間計算量についても説明します。
2. ブルートフォースアプローチ
このアプローチでは、入力文字列を繰り返し処理して、すべての部分文字列を見つけます。 同時に、部分文字列が回文であるかどうかを確認します。
public Set findAllPalindromesUsingBruteForceApproach(String input) {
Set palindromes = new HashSet<>();
for (int i = 0; i < input.length(); i++) {
for (int j = i + 1; j <= input.length(); j++) {
if (isPalindrome(input.substring(i, j))) {
palindromes.add(input.substring(i, j));
}
}
}
return palindromes;
}
上記の例では、部分文字列をその逆文字と比較して、それが回文であるかどうかを確認します。
private boolean isPalindrome(String input) {
StringBuilder plain = new StringBuilder(input);
StringBuilder reverse = plain.reverse();
return (reverse.toString()).equals(input);
}
もちろん、several other approachesから簡単に選択できます。
The time complexity of this approach is O(n^3).これは小さな入力文字列では許容できる場合がありますが、大量のテキストでパリンドロームをチェックする場合は、より効率的なアプローチが必要になります。
3. 集中化アプローチ
集中化アプローチの考え方は、consider each character as the pivot and expand in both directions to find palindromesです。
左側と右側の文字が一致する場合にのみ拡張し、文字列を回文として認定します。 それ以外の場合は、次の文字に進みます。
各キャラクターを回文の中心と見なす簡単なデモンストレーションを見てみましょう。
public Set findAllPalindromesUsingCenter(String input) {
Set palindromes = new HashSet<>();
for (int i = 0; i < input.length(); i++) {
palindromes.addAll(findPalindromes(input, i, i + 1));
palindromes.addAll(findPalindromes(input, i, i));
}
return palindromes;
}
上記のループ内で、両方向に展開して、各位置を中心とするすべての回文のセットを取得します。 ループ:でメソッドfindPalindromesを2回呼び出すと、偶数と奇数の両方の長さのパリンドロームが見つかります。
private Set findPalindromes(String input, int low, int high) {
Set result = new HashSet<>();
while (low >= 0 && high < input.length() && input.charAt(low) == input.charAt(high)) {
result.add(input.substring(low, high + 1));
low--;
high++;
}
return result;
}
The time complexity of this approach is O(n^2).これは、力ずくのアプローチを改善したものですが、次のセクションで説明するように、さらに改善することができます。
4. マナチャーのアルゴリズム
Manacher’s algorithmfinds the longest palindromic substring in linear time。 このアルゴリズムを使用して、回文であるすべての部分文字列を検索します。
アルゴリズムに飛び込む前に、いくつかの変数を初期化します。
まず、結果の文字列を文字配列に変換する前に、入力文字列を最初と最後に境界文字で保護します。
String formattedInput = "@" + input + "#";
char inputCharArr[] = formattedInput.toCharArray();
次に、2行の2次元配列radiusを使用します。1つは奇数の長さのパリンドロームの長さを格納し、もう1つは偶数の長さのパリンドロームの長さを格納します。
int radius[][] = new int[2][input.length() + 1];
次に、入力配列を反復処理して、位置i を中心とする回文の長さを見つけ、この長さをradius[][]に格納します。
Set palindromes = new HashSet<>();
int max;
for (int j = 0; j <= 1; j++) {
radius[j][0] = max = 0;
int i = 1;
while (i <= input.length()) {
palindromes.add(Character.toString(inputCharArr[i]));
while (inputCharArr[i - max - 1] == inputCharArr[i + j + max])
max++;
radius[j][i] = max;
int k = 1;
while ((radius[j][i - k] != max - k) && (k < max)) {
radius[j][i + k] = Math.min(radius[j][i - k], max - k);
k++;
}
max = Math.max(max - k, 0);
i += k;
}
}
最後に、配列radius[][]をトラバースして、各位置を中心とするパリンドローム部分文字列を計算します。
for (int i = 1; i <= input.length(); i++) {
for (int j = 0; j <= 1; j++) {
for (max = radius[j][i]; max > 0; max--) {
palindromes.add(input.substring(i - max - 1, max + j + i - 1));
}
}
}
このアプローチの時間計算量はO(n)です。
5. 結論
この簡単な記事では、パリンドロームである部分文字列を見つけるためのさまざまなアプローチの時間の複雑さについて説明しました。
いつものように、例の完全なソースコードはover on GitHubで入手できます。