C++で文字列を回文にするために必要な最小削除文字数を求める方法
問題の概要
長さ n の文字列が与えられたとき、その文字列を回文にするために削除が必要な最小の文字数を求めるのがこの問題の目的です。
たとえば、入力文字列が「abcda」の場合、先頭と末尾以外の2文字を削除すれば回文を作ることができます。
- 「b」と「c」を削除すると → 「ada」(回文になります)
- 「c」と「d」を削除すると → 「aba」(回文になります)
- 「b」と「d」を削除すると → 「aca」(回文になります)
アルゴリズムの考え方
この問題は「最長回文部分列(Longest Palindromic Subsequence: LPS)」の考え方を使うと効率的に解けます。手順は次のとおりです。
- 与えられた文字列の中で最も長い回文部分列の長さを求めます(これを
lpsSizeとします)。 - 削除が必要な最小文字数 = 文字列の長さ −
lpsSize
つまり、「削除せずにそのまま残せる最大の回文」を見つければ、残りの文字がすべて削除対象になるというシンプルな発想です。
C++での実装例
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
// 区間 [i, j] における最長回文部分列の長さを再帰的に求める
int lps(const string& s, int i, int j){
if (i == j) {
return 1; // 文字が1つだけならそれ自体が回文
}
if (s[i] == s[j] && i + 1 == j) {
return 2; // 隣接する2文字が一致する場合
}
if (s[i] == s[j]) {
return lps(s, i + 1, j - 1) + 2; // 両端が一致する場合
}
return max(lps(s, i, j - 1), lps(s, i + 1, j)); // どちらか一方を除外して比較
}
int minDeletion(const string& s){
int n = s.size();
int lpsSize = lps(s, 0, n - 1); // 文字列全体を対象にする
return n - lpsSize;
}
int main(){
cout << "Minimum characters to be deleted = "
<< minDeletion("abcda") << endl;
return 0;
}実行結果
上記のプログラムをコンパイルして実行すると、次の出力が得られます。
Minimum characters to be deleted = 2
計算量と改善のヒント
上記の素朴な再帰実装は、同じ区間の計算が何度も繰り返されるため、最悪の場合には指数時間 O(2n) がかかる点に注意が必要です。
そこで実務では、メモ化(動的計画法)を組み合わせるのが一般的です。各区間 (i, j) の計算結果を表やキャッシュに保存することで、時間計算量を O(n2) まで抑えることができます。文字列が長くなるケースでは、必ずこの最適化を検討しましょう。
-
C++で数値が回文数かどうかを判定する方法
この記事では、ある数値が回文数(パリンドローム)かどうかを判定する方法を解説します。回文数とは、前から読んでも後ろから読んでも同じになる数値のことです。例えば、12321 は回文数ですが、12345 は回文数ではありません。判定のロジックは非常にシンプルです。数値を逆順に並べ替え、元の数値と一致するかどうかを比較します。一致すれば回文数、一致しなければ回文数ではありません。より理解を深めるために、アルゴリズムを見ていきましょう。アルゴリズムisPalindrome(n) −入力 − 数値 n出力 − 数値が回文数であれば true、そうでなければ false 0, do rev
-
Pythonで文字列を回文にするために必要な最小挿入文字数を求めるプログラム
問題の概要 文字列 s が与えられたとき、その文字列を回文(前から読んでも後ろから読んでも同じになる文字列)にするために、最低何文字を挿入する必要があるかを求める問題です。 例えば、s = mad の場合、出力は 2 になります。「am」を挿入して「madam」にすれば回文になるためです。 解決のアプローチ この問題は、区間ごとに状態を管理する再帰的な動的計画法(DP)で効率よく解けます。以下の手順で考えます。 dp(i, j) という関数を定義します。これは、部分文字列 s[i..j] を回文にするために必要な最小挿入文字数を返します。 i >= j の場合(部分文字列が空、または