Javaの回文である部分文字列を探す

1.概要

このクイックチュートリアルでは、特定の文字列内の回文であるすべての部分文字列を検索するためのさまざまな方法を説明します。

各アプローチの時間的な複雑さにも注目します。

2.ブルートフォースアプローチ

このアプローチでは、入力文字列を単純に反復処理してすべての部分文字列を見つけます。同時に、サブストリングが回文かどうかを確認します。

public Set<String> findAllPalindromesUsingBruteForceApproach(String input) {
    Set<String> 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);
}

もちろん、https://www.baeldung.com/java-palindrome[その他のいくつかの方法]から簡単に選択できます。

  • このアプローチの時間的な複雑さはO(n ^ 3)です** これは小さな入力文字列では許容できるかもしれませんが、大量のテキストの回文をチェックする場合はもっと効率的なアプローチが必要です。

3.集中化アプローチ

集中化アプローチの考え方は、 各文字をピボットとみなし、両方向に拡大して回文を見つけることです

左右の文字が一致する場合にのみ拡張し、文字列を回文とします。それ以外の場合は、次の文字に進みます。

各キャラクターを回文の中心と見なす簡単なデモを見てみましょう。

public Set<String> findAllPalindromesUsingCenter(String input) {
    Set<String> 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<String> findPalindromes(String input, int low, int high) {
    Set<String> 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;
}
  • このアプローチの時間的な複雑さはO(n ^ 2)です** これは私たちのブルートフォースアプローチよりも改善されていますが、次のセクションで見るように、さらに良くすることができます。

4.管理者のアルゴリズム

https://en.wikipedia.org/wiki/Longest palindromic substring#Manacher’s__algorithmこのアルゴリズムを使用して、回文であるすべての部分文字列を見つけます。

アルゴリズムに入る前に、いくつか変数を初期化します。

まず、結果の文字列を文字配列に変換する前に、入力文字列の前後を境界文字で保護します。

String formattedInput = "@" + input + "#";
char inputCharArr[]= formattedInput.toCharArray();

次に、2行の2次元配列 radius を使用します。一方は奇数長の回文の長さを格納し、もう一方は偶数長の回文の長さを格納します。

int radius[][]= new int[2][input.length() + 1];

次に、入力配列を反復処理して、位置 __i を中心とする回文の長さを求め、この長さを radius[][]__に格納します。

Set<String> 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.まとめ

この簡単な記事では、回文である部分文字列を見つけるためのさまざまなアプローチの時間的な複雑さについて説明しました。

いつものように、例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/algorithms-miscellaneous-1[GitHubで利用可能]です。