2つの文字列s1とs2が与えられ、s1の並び替えがs2に含まれるかどうか(s1の順列がs2の部分文字列か)判定する問題。
Just a moment...
最初に思いついた解法
s1, s2 の長さをn1, n2とする。
s2から長さn1の文字列を切り出し、collections.Counterで得られる各文字の出現回数がs1と一致すればOK。
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
from collections import Counter
n1 = len(s1)
n2 = len(s2)
if n2 < n1:
return False
s1c = Counter(s1)
for i in range(n2 - n1 + 1):
temp = s2[i:i+n1]
s2c = Counter(temp)
if s1c == s2c:
return True
return False
一見ループ1回でO(N)の解法に見えるが、Counterの内部はどうなっているのか。
File not found · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
def _count_elements(mapping, iterable):
'Tally elements from the iterable.'
mapping_get = mapping.get
for elem in iterable:
mapping[elem] = mapping_get(elem, 0) + 1
文字列を1個ずつ数えており、Counterは辞書を構築するためにO(N)の時間を必要とする。
そのため上記の回答はO(N^2)になってしまう。
これを高速化するために、sliding windowsの考えを適応した。
すなわち、s2から長さn1の文字列でCounterを構築し、1文字ずつずらしながらCounterを足し引きしていく。
今回はCounterのソースコードを参考に、辞書の構築も自前で実装してみた。
class Solution:
def freqs(self, s):
f = {}
for i in s:
f.setdefault(i, 0)
f[i] += 1
return f
def checkInclusion(self, s1: str, s2: str) -> bool:
n1 = len(s1)
n2 = len(s2)
if n1 > n2:
return False
f1 = self.freqs(s1)
f2 = self.freqs(s2[:n1])
if f1 == f2:
return True
for i in range(n2 - n1):
left = s2[i]
right = s2[i+n1]
if f2[left] == 1:
del f2[left]
else:
f2[left] -= 1
f2.setdefault(s2[i+n1], 0)
f2[s2[i+n1]] += 1
if f1 == f2:
return True
return False
含まれる文字のカウント freqs でO(N)
ループがO(N)、値の足し引きはO(1)
トータルでO(N+N)=O(N)で解くことが出来るようになった。
参考
What is the time complexity of collections.Counter() in Python?
collection.Counter("bcdefffaa") returns output: Counter({'f': 3, 'a': 2, 'c': 1, 'b': 1, 'e': 1, 'd': 1}) Since the resu...
コメント