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

Pythonで文字列とその接尾辞との類似度の合計を求めるプログラム

問題の概要

文字列 s が与えられたとき、s とそのすべての接尾辞(末尾から1文字ずつ短くした部分文字列)との「類似度」の合計を求めます。ここで2つの文字列の類似度とは、両方の文字列に共通する最長の接頭辞(先頭からの一致部分)の長さのことです。

例として、入力が s = "pqpqpp" の場合を考えてみましょう。この文字列の接尾辞は次の6つです。

  • "pqpqpp"(元の文字列そのもの)
  • "qpqpp"
  • "pqpp"
  • "qpp"
  • "pp"
  • "p"

それぞれと元の文字列 "pqpqpp" との類似度は、順に 6、0、3、0、1、1 となります。したがって、求める合計は 6 + 0 + 3 + 0 + 1 + 1 = 11 です。

アプローチ:Zアルゴリズムによる線形時間の解法

この問題は Zアルゴリズム の考え方を利用すると効率的に解けます。Z配列の第 i 要素 z[i] は「位置 i から始まる接尾辞と、文字列の先頭が何文字一致しているか」を表します。これはまさに、今回求めたい「接尾辞との類似度」そのものです。

各接尾辞に対して毎回先頭から素朴に比較すると計算量は O(n²) になりますが、Zアルゴリズムでは、すでに判明している一致区間 [l, r] の情報を再利用することで、文字列全体を O(n) の線形時間で処理できます。

解き方の手順

  • length := 文字列 s の長さ
  • total := length(最初の接尾辞は文字列自身なので、類似度は length になる。これを初期値とする)
  • z := 0 を1つ含むリストとして初期化
  • l := 0、r := 0(現在わかっている一致区間の左右端)
  • k を 1 から length − 1 まで繰り返します。
    • k > r の場合(過去の情報が使えないため新規に比較):
      match := 0、index := k とし、index < length のあいだ s[index] と s[match] を比較しながら両方を進めます。不一致になったらループを抜けます。
    • 求めた match を z の末尾に追加します。
    • match > 0 の場合は、total に match を加算し、l := k、r := index − 1 として一致区間を更新します。
    • k ≤ r の場合(既存の一致区間を再利用できる場合):
      • z[k−l] < (r−k)+1 であれば、z[k−l] をそのまま z の末尾に追加し、total に加算します。
      • そうでなければ、match := r−k、index := r から比較を再開し、不一致まで進めた結果を z に追加して total に加算し、l := k、r := index − 1 を更新します。
  • 最後に total を返します。

Pythonでの実装例

以下が実際の実装です。

def solve(s):
    length = len(s)
    total = length

    z = [0]
    l = 0
    r = 0

    for k in range(1, length):
        if k > r:
            # 既存の一致区間が使えないため、先頭から直接比較する
            match = 0
            index = k
            while index < length:
                if s[index] == s[match]:
                    match += 1
                    index += 1
                else:
                    break
            z.append(match)
            if match > 0:
                total += match
                l = k
                r = index - 1
        else:
            # 一致区間 [l, r] の情報を再利用する
            if z[k - l] < (r - k) + 1:
                z.append(z[k - l])
                total += z[k - l]
            else:
                match = r - k
                index = r
                while index < length:
                    if s[index] == s[match]:
                        match += 1
                        index += 1
                    else:
                        break
                z.append(match)
                total += match
                l = k
                r = index - 1
    return total

s = "pqpqpp"
print(solve(s))

入力

"pqpqpp"

出力

11

まとめ

文字列とそのすべての接尾辞との類似度の合計を求める問題は、Zアルゴリズムを使うことで O(n) の計算量で解くことができます。ポイントは、すでに判明している一致区間 [l, r] の情報を再利用して、重複する比較を省略することです。この考え方は、文字列検索やパターンマッチングなど、さまざまな文字列処理の問題にも応用できるので、ぜひ押さえておきましょう。

  1. Pythonで文字列内に最も多く出現する文字とその出現回数を求める方法

    この記事では、文字列の中で最も多く出現する文字と、その出現回数を求める方法について、考え方と実装手順をわかりやすく解説します。 問題文 入力として与えられた文字列から、最も多く出現する文字と、その出現回数を特定します。 アプローチ Python標準ライブラリの collections.Counter を使い、「文字をキー・出現回数を値」とする辞書を作成します。 辞書の値(出現回数)の中から最大値を求め、その最大値に対応する文字を取得します。 それでは、実際の実装例を見ていきましょう。 実装例 from collections import Counter def find(input_)

  2. Pythonで文字列を分割・結合する方法|split()とjoin()の使い方を解説

    Pythonには、文字列の分割(split)と結合(join)を行うための組み込みメソッドが標準で用意されています。split()メソッドを使えば文字列を任意の区切り文字で簡単に分割でき、join()メソッドを使えば複数の文字列をひとつに連結できます。本記事では、それぞれの基本的な使い方をサンプルコードとともに解説します。 split Str.split() join Str1.join(str2) アルゴリズム ステップ1:文字列を入力する。 ステップ2:分割にはsplitメソッドを、結合にはjoin関数を使用する。 ステップ3:結果を出力する。 各メソッドのポイント split()