Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonで辞書(文字列リスト)からターゲット文字列を形成する方法の数を求めるプログラム

問題の概要

すべて同じ長さの文字列で構成されるリスト words と、文字列 target が与えられます。次のルールに従い、words を使って target を生成することを考えます。

  • target左から右へ順に構築します。
  • target の i 番目(0始まり)の文字を得るには、target[i]words[j][k](words の j 番目の文字列の k 番目の文字)と一致するとき、その文字を選択できます。
  • ある文字列の k 番目の文字を一度使用すると、それ以降はどの文字列でも x ≤ k を満たす x 番目の文字は使用できません。
  • この手順を target 全体が完成するまで繰り返します。

ここで求めたいのは、words から target を作る方法の総数です。答えは非常に大きな値になる可能性があるため、10^9 + 7 で割った余りを返してください。

たとえば、入力が words = ["pqqp", "qppq"]target = "qpq" のとき、出力は 4 になります。具体的な組み合わせは次の通りです。

  • "qpq" → インデックス 0("qppq")、インデックス 1("qppq")、インデックス 2("pqqp")
  • "qpq" → インデックス 0("qppq")、インデックス 1("qppq")、インデックス 3("qppq")
  • "qpq" → インデックス 0("qppq")、インデックス 2("qppq")、インデックス 3("qppq")
  • "qpq" → インデックス 1("pqqp")、インデックス 2("qppq")、インデックス 3("qppq")

解き方のアプローチ

この問題は深さ優先探索(DFS)を用いて解くことができます。大まかな流れは以下の通りです。

  • m := 各文字列の長さ(len(words[0]))
  • n := target の長さ
  • d := サイズ m のリストで、各要素は空の Counter(辞書)。位置ごとに各文字が何個含まれるかを記録します。
  • words の各文字列 w に対して、その各インデックス j と文字 c について d[j][c] += 1 を実行します。
  • 関数 dfs(i, j) を定義します。
    • i == n の場合(target をすべて作り終えた場合)は 1 を返す。
    • j == m の場合(利用できる文字の位置が尽きた場合)は 0 を返す。
    • それ以外の場合は (dfs(i, j+1) + dfs(i+1, j+1) * d[j][target[i]]) mod (10^9 + 7) を返す。
  • メイン処理では dfs(0, 0) の結果を返します。

ポイントは、dfs(i, j+1) で「j 番目の位置を使わずにスキップする」場合と、dfs(i+1, j+1) * d[j][target[i]] で「j 番目の位置にある target[i] と一致する文字のうち 1 つを使う」場合の合計を取っている点です。事前に位置ごとの文字の出現回数を Counter で集計しておくことで、一致する文字の選び方の数を即座に参照できます。

Python 実装例

理解を深めるために、以下の実装例を見てみましょう。

from collections import Counter

def solve(words, target):
    m, n = len(words[0]), len(target)
    d = [Counter() for _ in range(m)]
    for w in words:
        for j, c in enumerate(w):
            d[j][c] += 1

    def dfs(i, j):
        if i == n:
            return 1
        if j == m:
            return 0
        return (dfs(i, j+1) + dfs(i+1, j+1) * d[j][target[i]]) % int(1e9 + 7)

    return dfs(0, 0)

words = ["pqqp", "qppq"]
target = "qpq"
print(solve(words, target))

入力

words = ["pqqp", "qppq"], target = "qpq"

出力

4

補足:計算量の改善

上記の素朴な DFS は、入力サイズによっては指数時間かかる可能性があります。実践的には functools.lru_cache などでメモ化を追加するか、「dp[i][j] = target の i 文字目以降を、位置 j 以降の文字で作る方法の数」という二次元 DP に書き換えることで、計算量を O(n × m) まで抑えられます。位置ごとの文字数を前計算しておけば、状態遷移は定数時間で行えるためです。

  1. Pythonで文字列内の単語数を数えるプログラムの書き方【初心者向け】

    テキスト処理を行っていると、「入力された文字列の中に単語がいくつ含まれているのか」「特定の単語が何回出現するのか」を調べたい場面によく出会います。この記事では、Pythonを使って文字列内の単語数を数え、その結果を出力する複数の方法を、サンプルコードと実行結果付きでわかりやすく解説します。 基本的な考え方はシンプルです。「単語は空白・改行・タブなどの区切り文字で分けられている」ため、区切り文字の出現回数を数えれば、単語数は「区切り文字の数+1」として求められます。 方法1:forループを使う まずは最も基本的な方法です。文字列を1文字ずつ順番に調べ、区切り文字が見つかるたびにカウントを1

  2. Pythonで数値の2進表現における最長の連続する1の長さを求めるプログラム

    整数が与えられたとき、その2進表現(バイナリ表現)の中で最も長く連続する「1」の長さを求めるPythonプログラムを紹介します。 例 入力: n = 15 出力: 4 15 の2進表現は 1111 です。 この場合、「1」が4つ連続しているため、答えは4となります。 アルゴリズム 数値を入力として受け取ります。 カウンタ変数 c を 0 で初期化します。 n が 0 になるまでの反復回数を数えます。 ビット演算 n & (n << 1) を行うことで、1の連続列の長さが毎回1つずつ短くなっていきます。 アルゴリズムのポイント この手法の鍵となるのは n &