回文判定は「左右対称かどうか」を見るシンプルなアルゴリズム
回文(かいぶん)は、「前から読んでも後ろから読んでも同じ文字列」のことです。
英語なら level, noon、日本語なら「たけやぶやけた」「しんぶんし」などが有名ですね。
回文判定は、一見お遊びのようですが、アルゴリズムの練習としてとても優秀です。
なぜなら、「文字列」「インデックス」「ループ」「条件分岐」「境界条件」といった、
プログラミングの基礎が全部詰まっているからです。
ここでは、まずは一番簡単な実装から入り、
そのあとで「効率の良い書き方」「実務で使える形」まで一気に持っていきます。
一番簡単な回文判定:反転して同じかどうか比べる
文字列反転ユーティリティをそのまま使う
すでに「文字列反転」のユーティリティがあるなら、
回文判定は驚くほど簡単に書けます。
public final class PalindromeUtil {
private PalindromeUtil() {}
public static boolean isPalindromeSimple(String text) {
if (text == null) {
return false;
}
String reversed = new StringBuilder(text).reverse().toString();
return text.equals(reversed);
}
}
Java使い方はこうなります。
System.out.println(PalindromeUtil.isPalindromeSimple("level")); // true
System.out.println(PalindromeUtil.isPalindromeSimple("noon")); // true
System.out.println(PalindromeUtil.isPalindromeSimple("abc")); // false
System.out.println(PalindromeUtil.isPalindromeSimple("あいいあ")); // true
System.out.println(PalindromeUtil.isPalindromeSimple(null)); // false
Javaここで押さえておきたいポイントは二つです。
一つ目は、「反転して同じなら回文」という、定義そのものをコードにしていることです。
回文の定義をそのまま実装しているので、直感的で分かりやすい書き方になっています。
二つ目は、「null をどう扱うかを決めている」ことです。
ここでは「null は回文ではない」として false を返しています。
このあたりのポリシーを自分で決めて、コードに反映させる感覚が大事です。
もう一歩踏み込む:左右から内側に向かって比較する
反転用の文字列を作らずに判定する方法
さきほどの実装は分かりやすい反面、
「反転した文字列を丸ごと1つ作る」というコストがかかります。
回文判定だけが目的なら、
左右から1文字ずつ比較していくだけで十分です。
public final class PalindromeUtil {
private PalindromeUtil() {}
public static boolean isPalindrome(String text) {
if (text == null) {
return false;
}
int left = 0;
int right = text.length() - 1;
while (left < right) {
char cLeft = text.charAt(left);
char cRight = text.charAt(right);
if (cLeft != cRight) {
return false;
}
left++;
right--;
}
return true;
}
}
Java使い方は同じです。
System.out.println(PalindromeUtil.isPalindrome("level")); // true
System.out.println(PalindromeUtil.isPalindrome("noon")); // true
System.out.println(PalindromeUtil.isPalindrome("abc")); // false
System.out.println(PalindromeUtil.isPalindrome("あいいあ")); // true
Javaここで重要なポイントをしっかり噛み砕きます。
一つ目は、「left と right という“両端のインデックス”を使っている」ことです。left は先頭から、right は末尾からスタートし、
毎回1つずつ内側に進めていきます。
二つ目は、「while (left < right) という条件の意味」です。
左右が交差した時点(left >= right)で、
すべてのペアをチェックし終わっているので、ループを終えてよい、ということです。
ここを <= にしてしまうと、中央の1文字を自分自身と比較することになり、
動作としては問題ないものの、無駄な1回が増えます。
三つ目は、「途中で1箇所でも違えば即座に false を返している」ことです。
回文ではないと分かった瞬間に処理を打ち切ることで、
無駄な比較をしない、という効率の良さも手に入れています。
この「左右から内側に向かって比較する」パターンは、
回文判定だけでなく、配列やリストの対称性チェックなど、
いろいろな場面で使える強力な基本パターンです。
実務寄りの回文判定:空白や大文字小文字を無視する
「見た目として回文かどうか」を判定したい場合
現実のテキストでは、空白や記号、大文字小文字の違いを無視して
「見た目として回文かどうか」を判定したいことがあります。
例えば、英語の有名な回文にこんなものがあります。
Never odd or even
空白と大文字小文字を無視すると、neveroddoreven になり、これは回文です。
これを判定するユーティリティを書いてみます。
public final class PalindromeUtil {
private PalindromeUtil() {}
public static boolean isPalindromeIgnoringCaseAndSpaces(String text) {
if (text == null) {
return false;
}
String normalized = text
.replace(" ", "") // 空白を除去
.toLowerCase(); // 小文字に統一
int left = 0;
int right = normalized.length() - 1;
while (left < right) {
char cLeft = normalized.charAt(left);
char cRight = normalized.charAt(right);
if (cLeft != cRight) {
return false;
}
left++;
right--;
}
return true;
}
}
Java使い方はこうです。
System.out.println(
PalindromeUtil.isPalindromeIgnoringCaseAndSpaces("Never odd or even")
); // true
System.out.println(
PalindromeUtil.isPalindromeIgnoringCaseAndSpaces("A man a plan a canal Panama")
); // true っぽく扱いたい例(実際には記号もあるので、さらに正規化が必要)
Javaここでの重要ポイントは、「正規化(normalize)」という考え方です。
判定ロジックそのものは、先ほどと同じ「左右から比較」です。
違うのは、その前に
空白を消す
大文字小文字を揃える
という「前処理」を挟んでいることです。
実務の文字列処理では、この「前処理(正規化)」がとても大事になります。
回文に限らず、検索、比較、バリデーションなど、
「何を無視して、何を区別するか」を決めてから処理を書く、という感覚を持っておくと、
設計が一気にきれいになります。
Unicodeと回文:深追いしすぎないための知識
絵文字や結合文字をどう扱うか
文字列反転のときと同じく、
「1文字=1char ではない」問題は、回文判定にも関わってきます。
例えば、絵文字や一部の漢字は、内部的には2つの char(サロゲートペア)で表現されます。
また、「文字+濁点」「文字+結合記号」のようなケースもあります。
本気で「見た目の1文字単位で回文かどうか」を判定しようとすると、codePoint 単位で扱う必要があります。
public static boolean isPalindromeByCodePoint(String text) {
if (text == null) {
return false;
}
int[] cps = text.codePoints().toArray();
int left = 0;
int right = cps.length - 1;
while (left < right) {
if (cps[left] != cps[right]) {
return false;
}
left++;
right--;
}
return true;
}
Javaただし、ここまでやるかどうかは要件次第です。
多くの業務システムでは、
「回文判定をそこまで厳密にやる」場面はほとんどありません。
大事なのは、「char ベースの処理には限界がある」ということを知っておくこと。
知らないまま「完璧だ」と思い込むのが一番危険です。
「必要になったら codePoint で考える」という引き出しを持っておけば十分です。
回文判定の実務での使いどころと、学習としての価値
正直に言うと、「回文判定そのもの」が業務要件として出てくることはあまり多くありません。
ただし、次のような場面では役に立ちます。
テキスト処理や検索機能のテストデータとして
アルゴリズムの練習問題として(新人教育など)
左右対称性を使うロジックのサンプルとして
そして何より、回文判定をちゃんと書けるようになると、
文字列の長さとインデックスの関係
左右から中央に向かうループの書き方
途中で打ち切る条件の考え方
前処理(正規化)と本処理の分離
といった、実務でガンガン使う思考パターンが身につきます。
まとめ:回文判定ユーティリティで鍛えたい感覚
回文判定は、「前から読んでも後ろから読んでも同じか」を見るだけのシンプルな処理ですが、
その裏側には、文字列処理のエッセンスが詰まっています。
まずは
文字列を反転して equals で比べるシンプル版
左右から内側に向かって比較する効率の良い版
の二つを書いてみてください。
そのうえで、
空白や大文字小文字を無視するための正規化
必要に応じて codePoint を意識する
といった発展形にも触れてみると、
「文字列を自在に扱える感覚」が一気に育ちます。
もし今、あなたのコードのどこかに
「左右対称かどうか」「前後が一致しているか」を見る処理があれば、
一度この回文判定のパターンに当てはめて書き直してみてください。
その小さな練習が、アルゴリズムに強いエンジニアへの、確かな一歩になります。
