Pythonで同種(ホモジニアス)な部分文字列の数をカウントするプログラム
文字列 s が与えられたとき、その中に含まれる「同種(homogenous)な部分文字列」の総数を求める問題を考えます。同種な文字列とは、構成するすべての文字が同一である文字列のことです。答えは非常に大きな値になる可能性があるため、10^9+7 で割った余りを返します。
問題の例
たとえば、入力が s = "xyyzzzxx" の場合、出力は 13 になります。同種の部分文字列は次のように数えられます。
"x" が 3 回
"xx" が 1 回
"y" が 2 回
"yy" が 1 回
"z" が 3 回
"zz" が 2 回
"zzz" が 1 回
したがって、(3 + 1 + 2 + 1 + 3 + 2 + 1) = 13 となります。
解法のアプローチ
この問題を解くために、以下の手順に従います。
sの末尾に番兵(センチネル)として"@"を連結します。これにより、ループ内で最後の連続区間も確実に締めくくられるようになります。空の辞書
hを用意し、prev := s[0]、c := 1と初期化します。sのインデックス 1 以降の各文字iについて処理を行います。prevとiが異なる場合:これまでの連続文字列prev * cをキーとして辞書hに出現回数を記録し、カウントcを 1 にリセットします。prevとiが同じ場合:カウントcを 1 増やします。最後に
prev := iと更新します。
合計値
fin := 0とします。辞書
hの各キー(連続文字列)について、長さtの連続区間から作れる部分文字列の総数k = t + (t-1) + … + 1を計算し、fin += k * h[i]として加算します。fin mod (10^9+7)を返します。
ここでポイントとなるのは、長さ t の同一文字の連続区間からは、t + (t-1) + … + 1 = t(t+1)/2 個の同種部分文字列が作れるという点です。この性質(三角数)を使うことで、各区間ごとに効率よく個数を集計できます。
実装例
理解を深めるために、以下のPythonコードを見てみましょう。
def solve(s):
s += "@"
h = {}
prev = s[0]
c = 1
for i in s[1:]:
if prev != i:
if prev * c in h:
h[prev * c] += 1
else:
h[prev * c] = 1
c = 1
else:
c += 1
prev = i
fin = 0
for key in h:
t = len(key)
k = 0
while t != 0:
k += t
t -= 1
fin += k * h[key]
return fin % 1000000007
s = "xyyzzzxx"
print(solve(s))
入力
"xyyzzzxx"
出力
13
補足:よりシンプルな最適化版
辞書を使わず、連続区間ごとに三角数の公式 t(t+1)/2 を直接適用すれば、より簡潔に書けます。
def solve(s):
MOD = 10**9 + 7
total = 0
run = 1
for i in range(1, len(s)):
if s[i] == s[i - 1]:
run += 1
else:
total += run * (run + 1) // 2
run = 1
total += run * (run + 1) // 2
return total % MOD
print(solve("xyyzzzxx")) # 13
どちらの方法でも計算量は O(n) となり、長い文字列に対しても高速に動作します。
-
Pythonで括弧の各深さごとの文字数をカウントするプログラムの作成方法
文字列 s が与えられます。この文字列は「X」「(」「)」の3種類の文字のみで構成されており、括弧は必ずバランスが取れていて、その間に「X」が含まれています。また、括弧は再帰的にネストしている場合もあります。 この課題では、最も浅い深さから最も深い深さへ向かって、各括弧の深さごとに「X」の個数を求めます。 入力例と出力例 たとえば、入力が s = (XXX(X(XX))XX) の場合、出力は [5, 1, 2] になります。 深さ0(最も外側の括弧の中)には「X」が5個 深さ1には「X」が1個 深さ2(最も内側の括弧の中)には「X」が2個 解き方のアプローチ この問題は、次の手順で解くこと
-
Pythonでn個のノードから構成できる二分探索木(BST)の数を求める方法
問題の概要互いに異なるn個のノードが与えられたとき、それらを二分探索木(BST:Binary Search Tree)として配置する方法が何通りあるかを求めることを考えます。二分探索木には「左部分木には常に親より小さい値が、右部分木には常に親より大きい値が格納される」という重要な性質があります。この問題を解くには、カタラン数(Catalan Number)を利用します。カタラン数 C(n) は、n個の異なるキーから構成できる二分探索木の総数を正確に表すことが知られています。計算式は次のとおりです。$$C(n)=\frac{(2n)!}{(n+1)!\times n!}$$例えば、入力が n =