C++で回文を壊す:辞書順最小の非回文文字列を作るアルゴリズム
問題概要
回文(前から読んでも後ろから読んでも同じになる文字列)が与えられます。この文字列に対して、ちょうど1文字を任意の小文字の英字に置き換え、回文ではなくなる文字列のうち辞書順最小のものを作ります。そして、その結果得られる最終的な文字列を求めてください。どうしても回文を壊せない場合は、空文字列を返します。
例えば、入力が "abccba" の場合、出力は aaccba となります。
解法のアプローチ
この問題は貪欲法(グリーディ法)を使うことで効率的に解けます。辞書順最小の文字列を作るには、「可能な限り前方の文字を小さくする」という性質を利用するのがポイントです。
アルゴリズムの手順
changed := falseと初期化する- 文字列の長さが1の場合は空文字列を返す(1文字の置換では必ず再び回文になるため)
i := 0、j := 文字列の長さ - 1とするi < jの間、次を繰り返す:s[i]が'a'でなければ、s[i]を'a'に書き換えてsを返すiを1増やし、jを1減らす
- ループを抜けた場合(すべての文字が
'a'の場合)、末尾の文字を'b'に変更する sを返す
なぜこの方法が有効なのか
- 英小文字には
'a'より小さい文字が存在しないため、左側から見て最初に見つかった'a'以外の文字を'a'に置き換えるのが最適です。 - すべての文字が
'a'の場合(例:"aaa")、辞書順最小を保ちつつ回文を壊すには、末尾の文字を'b'にするしかありません。 - 長さ1の文字列は、どの1文字を置き換えても回文のままなので、条件を満たす答えは存在せず、空文字列を返します。
C++での実装例
以下の実装を見ると、理解がさらに深まるでしょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
string breakPalindrome(string s) {
bool changed = false;
if(s.size() == 1)return "";
int i = 0, j = s.size() - 1;
bool leftA = true;
bool rightA = true;
while(i < j){
if(s[i] != 'a'){
s[i] = 'a';
return s;
}
i++;
j--;
}
s[s.size() - 1] = 'b';
return s;
}
};
main(){
Solution ob;
cout << (ob.breakPalindrome("abccba"));
}
入力
"abccba"
出力
aaccba
計算量
- 時間計算量:O(n) ― 文字列を高々1回走査するだけです。
- 空間計算量:O(1) ― 入力文字列を直接書き換えるため、追加のメモリはほぼ不要です。
-
C++で文字列をトークン化する方法:stringstreamとgetline()による分割テクニック
この記事では、C++における文字列のトークン化(分割)の方法について解説します。C言語では、文字配列に対してstrtok()関数を使用することで文字列を分割できましたが、C++ではstd::stringクラスを扱うため、少し異なるアプローチが必要です。C++の機能を活用して文字列を分割するには、まずstd::stringをstringstream(文字列ストリーム)に変換します。その後、getline()関数を使うことで、指定した区切り文字(デリミタ)ごとに文字列を切り出すことができます。getline()関数は、以下の3つの引数を受け取ります。入力元となる文字列ストリーム出力結果を格納する文
-
C++で文字列をトークン化(分割)する2つの方法を解説
文字列のトークン化(分割)とは、1つの文字列を区切り文字(スペースやカンマなど)を基準に、複数の部分文字列へ分割する処理のことです。C++では、標準ライブラリだけでもいくつかの方法で実現できます。本記事では、代表的な2つの方法をサンプルコード付きで紹介します。方法1:stringstreamを使って空白で分割する1つ目の方法は、stringstreamを使ってスペースで区切られた単語を順に読み取る方法です。この方法はやや制限がありますが、適切なチェックを加えれば十分に目的を果たすことができます。サンプルコード#include <vector> #include <string