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

PythonでK類似文字列の最小スワップ回数Kを求める方法【BFSで解く】

問題の概要

2つの文字列 st があるとします。s の中の2文字の位置をちょうどK回入れ替えることで st と同一の文字列にできるとき、この2つの文字列はK類似(K-similar)であると定義されます。

ここでは、互いにアナグラムの関係にある2つの文字列 st が与えられ、st がK類似となる最小のKを求めることを目標とします。

たとえば、入力が s = "abc"t = "bac" の場合、出力は 1 になります。「a」と「b」を1回入れ替えるだけで "abc""bac" と変換できるためです。

解法の考え方:幅優先探索(BFS)

この問題は、各文字列の状態をノード、1回のスワップで移行できる関係をエッジとみなしたグラフとして捉えると、幅優先探索(BFS)で最短のスワップ回数を求められます。開始状態 s から出発し、目標状態 t に到達するまでの最少ステップ数がそのまま答えになります。

neighbors() 関数の役割

引数で受け取った文字列について、1回のスワップで作れる次の状態候補をすべて列挙するジェネレータ関数です。処理の流れは次のとおりです。

  • 各インデックス i とその文字 c を先頭から走査し、ct[i] と異なる最初の位置でループを抜けます。それより手前の位置はすでに正しい文字が揃っているため、そこを崩すスワップは無駄になるからです。
  • 続いて ji + 1 から末尾まで動かし、new_data[j]t[i] と一致する場合に new_data[i]new_data[j] を入れ替えます。入れ替えた文字列を yield で返した後、元に戻して次の候補を探します。

BFS本体の処理手順

  1. キュー q を作成し、ペア (s, 0) を挿入します(第2要素は現在のスワップ回数)。
  2. 既訪問管理用の集合 seen を作成し、s を登録しておきます。
  3. q が空でない限り、以下を繰り返します。
    • キューの先頭から (u, swap_cnt) を取り出します。
    • ut と一致していれば、swap_cnt を返します。
    • neighbors() が返す各候補 v について、未訪問なら seen に追加し、(v, swap_cnt + 1) をキューの末尾へ追加します。

探索が完了しても t に到達できない場合は 0 を返します。

Pythonでの実装例

from collections import deque

def solve(s, t):
    def swap(data, i, j):
        data[i], data[j] = data[j], data[i]

    def neighbors(new_data):
        for i, c in enumerate(new_data):
            if c != t[i]:
                break

        for j in range(i + 1, len(new_data)):
            if new_data[j] == t[i]:
                swap(new_data, i, j)
                yield "".join(new_data)
                swap(new_data, i, j)

    q = deque([(s, 0)])
    seen = set([s])
    while q:
        u, swap_cnt = q.popleft()
        if u == t:
            return swap_cnt
        for v in neighbors(list(u)):
            if v not in seen:
                seen.add(v)
                q.append((v, swap_cnt + 1))
    return 0

s = "abc"
t = "bac"
print(solve(s, t))

入力

s = "abc", t = "bac"

出力

1

計算量と実装のポイント

最悪の場合、状態数は文字列長 n に対して最大 n! 通りとなり、計算量は O(n! × n²) 程度に増大します。ただし、BFSは必ず最短のスワップ回数を見つけることが保証されており、既訪問集合 seen によって同じ状態を二度と探索しないため、重複状態が多い場合でも効率よく動作します。

さらに、neighbors() で「まだ一致していない最初の位置」だけをスワップ対象にすることで、無駄な候補を大幅に削減できる点も、この実装における重要な工夫です。

  1. Pythonで有向グラフにおける最大の色の値を見つけるプログラム

    この問題では、n個の色付きノードとm個の異なるエッジから構成される有向グラフが与えられます。ノードには0からn-1までの番号が付けられています。小文字アルファベットのみで構成された文字列colがあり、col[i]はグラフ内のi番目のノード(0インデックス)の色を表します。さらに、edges[j] = (u, v)という形式のエッジリストが与えられ、ノードuからノードvへ向かう有向エッジが存在することを示します。 グラフにおける「有効なパス」とは、ノードの系列 x₁ から xₖ までの並びであり、隣接する任意の xᵢ と xᵢ₊₁ の間に、xᵢ から xᵢ₊₁ へ向かう有向エッジが存在するものを

  2. Pythonでリスト内の最小値を見つける方法を解説

    この記事では、リストの中から最小の数値を見つける方法について、具体的なサンプルコードとともに詳しく解説します。問題の概要問題: 数値のリストが与えられたとき、その中に含まれる最も小さい数値を画面に表示すること。この問題を解くアプローチは主に2つあります。ひとつは sort() メソッドを使ってリストを昇順に並べ替え、先頭の要素(インデックス0)を取得する方法。もうひとつは、Pythonに標準で用意されている組み込み関数 min() を使う方法です。それぞれ順番に見ていきましょう。方法1:sort()メソッドで並べ替えて最小値を取得するまずはリストを昇順にソートし、先頭の要素を取り出す方法です。