Pythonでk種類の一意な文字を含む最長部分文字列を求める方法【スライディングウィンドウ法】
問題の概要
文字列が与えられたとき、ちょうどk個の一意な(重複しない)文字を含む最長の部分文字列を返すことを考えます。条件を満たす最長の部分文字列が複数存在する場合は、そのうちのどれか1つを返せば問題ありません。
例えば、入力が s = "ppqprqtqtqt"、k = 3 の場合、出力は長さ7の「rqtqtqt」となります。
解法の考え方:スライディングウィンドウ法
この問題はスライディングウィンドウ(尺取り法)と呼ばれる手法で効率的に解けます。ウィンドウの右端を1文字ずつ伸ばしていき、一意な文字の種類数が制約を超えたら左端を縮める、という操作を繰り返すことで答えを求めます。
アルゴリズムの手順
- N := 26 とする(英小文字26種類に対応)
- 補助関数 is_ok(count, k) を定義する
- val := 0 で初期化する
- i を 0 から N-1 までループし、count[i] > 0 なら val を 1 増やす(現在使われている文字の種類数を数える)
- k >= val が成立すれば true を返す
- メイン処理では以下を実行する
- unique := 0、size := 文字列 s の長さとする
- サイズ N の配列 count を 0 で初期化する
- i を 0 から size-1 までループし、各文字の出現回数をカウントする。初めて現れた文字であれば unique を 1 増やす
- unique < k の場合、条件を満たす部分文字列が存在しないため、そこで処理を終了する
- start := 0、end := 0、window_length := 1、window_start := 0 で初期化する
- count を再び 0 で初期化し、s[0] のカウントを 1 にする
- i を 1 から size-1 までループする
- s[i] のカウントを 1 増やし、end を 1 増やす(ウィンドウを右へ拡張)
- is_ok(count, k) が false の間、s[start] のカウントを 1 減らして start を 1 増やす(ウィンドウを左側から縮小)
- end - start + 1 > window_length であれば、window_length と window_start を更新する
- window_start から window_start + window_length までの部分文字列を返す
Pythonでの実装例
それでは、実際のコードを見てみましょう。
N = 26
def is_ok(count, k):
val = 0
for i in range(N):
if count[i] > 0:
val += 1
return (k >= val)
def k_unique_chars(s, k):
unique = 0
size = len(s)
count = [0] * N
for i in range(size):
if count[ord(s[i]) - ord('a')] == 0:
unique += 1
count[ord(s[i]) - ord('a')] += 1
if unique < k:
return "Not sufficient characters"
start = 0
end = 0
window_length = 1
window_start = 0
count = [0] * len(count)
count[ord(s[0]) - ord('a')] += 1
for i in range(1, size):
count[ord(s[i]) - ord('a')] += 1
end += 1
while not is_ok(count, k):
count[ord(s[start]) - ord('a')] -= 1
start += 1
if end - start + 1 > window_length:
window_length = end - start + 1
window_start = start
return s[window_start:window_start + window_length]
s = "ppqprqtqtqt"
k = 3
print(k_unique_chars(s, k))
実行結果
入力
"ppqprqtqtqt", 3
出力
rqtqtqt
計算量のポイント
この実装では、各文字に対してウィンドウへの追加と除去がそれぞれ高々1回ずつしか行われないため、時間計算量は O(n) となります。また、使用しているのは固定サイズ26の配列だけなので、空間計算量も O(1) です。すべての部分文字列を総当たりで調べる O(n²) の素朴なアプローチと比べて大幅に効率化できる点が、この手法の大きな魅力です。
-
【Python】正規表現(Regex)で文字列内の「1(0+)1」パターンをすべて検索する方法
このチュートリアルでは、Pythonの正規表現(regex)を使って、文字列内に含まれる「1(0+)1」というパターンをすべて検出するプログラムを作成します。Pythonには正規表現を扱うためのreモジュールが標準で用意されており、これを活用することでパターンマッチングを簡単に実装できます。 サンプルケース まず、どのような動作になるのかサンプルを見てみましょう。 入力:string = Sample 1(0+)1 string with 1(0+)1 unnecessary patterns 1(0+)1出力:パターンの一致数:3件[1(0+)1, 1(0+)1, 1(0+)1] それでは、
-
Pythonで文字列内のn番目に出現する部分文字列の位置を見つける方法
Pythonでは、split()メソッドを活用することで、文字列内にn番目に出現する部分文字列の位置(インデックス)を簡単に求めることができます。基本的な考え方手順は以下の通りです。対象の部分文字列を区切り文字として、最大 n+1 回だけ文字列を分割します。分割後のリストの要素数が n+1 より大きければ、その部分文字列は少なくとも n 回以上出現していることになります。出現位置は、「元の文字列の長さ − 最後の分割部分の長さ − 部分文字列の長さ」というシンプルな式で計算できます。コード例def findnth(string, substring, n): parts = strin