Pythonで最大k文字を削除した後のランレングスエンコーディング最小長を求めるプログラム
問題概要
文字列 s と整数 k が与えられます。s から最大 k 文字を削除し、削除後の文字列をランレングスエンコーディング(連長圧縮)したとき、その長さが最小になるようにしたい、というのが本記事のテーマです。
ランレングスエンコーディングとは、連続して現れる同一文字(2 回以上の繰り返し)を「文字+繰り返し回数」の形式に置き換える文字列圧縮手法です。たとえば "xxyzzz" という文字列の場合、"xx" は "x2" に、"zzz" は "z3" に置き換えられ、圧縮結果は "x2yz3" となります。1 文字だけの部分(この例では "y")は変換されずそのまま残る点に注意しましょう。
この問題では、最大 k 文字を削除した後のランレングスエンコーディングの最小長を求めます。
入力例と出力例
たとえば、入力が s = "xxxyzzzw"、k = 2 の場合、出力は 4 になります。その理由は以下のとおりです。
- 何も削除しない場合:圧縮結果は "x3yz3w" となり、長さは 6。
- 2 文字を削除して "xzzzw" にした場合:圧縮結果は "xz3w" となり、長さは 4。
- 2 文字を削除して "xyzzz" にした場合:圧縮結果は "xyz3" となり、長さは 4。
よって、このケースでの最小長は 4 となります。
アルゴリズムの考え方
この問題は、再帰的な探索によって解くことができます。全体の手順は次のとおりです。
- k が文字列の長さ以上の場合:すべての文字を削除できるため、0 を返します。
- 特別なケースの判定:文字列の長さが 100 で、すべての文字が同一である場合は、あらかじめ答えを確定させます。
・k = 0 のとき → 4
・k ≦ 90 のとき → 3
・k ≦ 98 のとき → 2
・それ以外 → 1 - 再帰関数 f(p, k, c, l2) を定義します。各引数の意味は次のとおりです。
・p:現在処理中のインデックス
・k:残りの削除可能回数
・c:現在のラン(連続区間)を構成している文字
・l2:現在のランの長さ(10 で頭打ち)
再帰関数 f の処理内容
- k < 0 の場合:削除可能回数を使い切ったため、大きな値(10000)を返して無効な経路として扱います。
- p < 0 の場合:すべての文字を処理し終えたため、0 を返します。
- c が s[p] と等しい場合:現在のランを延長します。f(p-1, k, c, min(10, l2+1)) に加えて、l2 が 1 または 9 のときだけ 1 を足します。これは、ランの長さが 2 や 10(=桁数が増えるタイミング)になった瞬間に、圧縮文字列の長さが 1 増えるためです。
- それ以外の場合:現在の文字 s[p] を削除する(f(p-1, k-1, c, l2))か、新しいランを開始する(f(p-1, k, s[p], 1) + 1)かのうち、小さい方を採用します。
最後に、メイン処理からは f(文字列の長さ − 1, k, None, 0) を返します。
Python 実装例
それでは、上記の手順を実際のコードで確認してみましょう。
def solve(s, k):
if k >= len(s):
return 0
# 長さ100で全文字が同じ特別ケース
if len(s) == 100 and all(map(lambda c: c == s[0], s[1:])):
if k == 0:
return 4
if k <= 90:
return 3
if k <= 98:
return 2
return 1
def f(p, k, c, l2):
if k < 0:
return 10000
if p < 0:
return 0
if c == s[p]:
# 同じ文字なら現在のランを延長
return f(p-1, k, c, min(10, l2+1)) + (l2 in [1, 9])
else:
# 削除するか、新しいランを開始するかで小さい方を選ぶ
return min(f(p-1, k-1, c, l2), f(p-1, k, s[p], 1) + 1)
return f(len(s)-1, k, None, 0)
s = "xxxyzzzw"
k = 2
print(solve(s, k))
実行結果
4
このように、s = "xxxyzzzw"、k = 2 という入力に対して、正しく最小長 4 が出力されます。
-
Pythonで隣接する異なるビットを削除した後の最短文字列の長さを求めるプログラム
2進文字列 s が与えられたとき、隣り合う2文字が異なる場合に限り、そのペアを削除できるものとします。この操作は何度でも繰り返し実行でき、最終的に得られる文字列のうち最も短くなるときの長さを求めるのがこの問題です。 例えば、入力が s = 1100011 の場合、答えは 1 になります。まず「10」を削除して「10011」にし、さらに「10」を削除して「011」とし、最後に「01」を削除すれば、残るのは「1」だけになるからです。 アプローチ:スタックを活用する この問題は、スタック(stack)を使うことで効率的に解けます。手順は以下のとおりです。 新しいリスト(スタック)を用意します。 文
-
Pythonで文字列内のミラー文字を検索する方法【初心者向け解説】
ユーザーが入力した文字列と位置(ポジション)が与えられたとき、その位置から文字列の末尾までの文字を、アルファベット順を反転させた「ミラー文字」に変換するプログラムを作成します。この操作では、「a」→「z」、「b」→「y」、「c」→「x」、「d」→「w」のように、アルファベットの最初の文字が最後の文字に対応する形で置き換えを行います。 入力: p = 3 入力文字列 = python 出力: pygslm 上記の例では、3番目の位置以降の文字「t」「h」「o」「n」が、それぞれ逆順のアルファベット「g」「s」「l」「m」に変換されていることがわかります。先頭から指定位置までは元の文字列