Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions grind75_hard/01_76_Minimum Window Substring/level_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ポインタを2つ使い条件を満たす範囲を見つける。
# まずtの文字全てが含まれるwindowを見つけるために、rightを進めていく
# tの文字全てが含まれるwindowを見つけたら、leftを進めて無駄な文字を削除していく
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_count = defaultdict(int)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t_count に何が含まれているのか、変数名からだけでは想起できませんでした。 char をどこかに入れてはいかがでしょうか? num_chars_in_t あたりが良いと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘いただいた変数名の案は非常に分かりやすいです。
ありがとうございます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メモリのランダムアクセスを避け、定数倍高速化するため、 t_count = [0] * 128 としたいです。ですが、 t_count[ord(c)] += 1 をしなければならず、これはこれで微妙だなとも感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メモリのランダムアクセスを避け、定数倍高速化するため

メモリが並んでいる方がt_count[ord(c)] += 1のときにアドレスを見つけるのが早くなるということでしょうか?
メモリのアクセス速度についてイマイチ理解できていないです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ListとHash Tableでメモリ上に各要素がどう配置されるかということだと思います。Listだと隣り合う要素はメモリ上も隣にあります。Hash Tableだとハッシュ関数で計算される位置に要素があるので、要素としての隣であってもメモリ場は隣になりです。

Hash Tableだと単純に目的の要素を探すのにハッシュ関数を実行する必要があるので、その分Listより定数倍遅くなりますし、あとは空間局所性が低いのでキャッシュのヒット率が下がります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
メモリ上の配置の言及はイメージしやすかったです。
一応下のコメント欄に回答は頂いていましたが、より理解が深まりました。

for c in t:
t_count[c] += 1
window_count = defaultdict(int)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらも num_chars_in_window あたりが良いと思います。

left = 0
right = 0
match_num = 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number_of_matches の略で num_matches としたほうが自然だと思ました。

min_length = math.inf, None, None
while right < len(s):
if s[right] in t_count:
window_count[s[right]] += 1
if window_count[s[right]] == t_count[s[right]]:
match_num += 1
while left <= right and match_num == len(t_count):
if min_length[0] > right - left:
min_length = right - left, left, right
if s[left] in t_count:
window_count[s[left]] -= 1
if window_count[s[left]] < t_count[s[left]]:
match_num -= 1
left += 1
right += 1
if min_length[0] == math.inf:
return ""
_, left, right = min_length
return s[left : right + 1]
29 changes: 29 additions & 0 deletions grind75_hard/01_76_Minimum Window Substring/level_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# rightをfor文で実装
# 最小のwindowのindexのみを保持するように修正
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_count = defaultdict(int)
for c in t:
t_count[c] += 1
left = 0
match_num = 0
window_count = defaultdict(int)
min_window_index = (left, math.inf)
for right, c in enumerate(s):
if c not in t_count:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この if 文は入れても入れなくても良いように感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全文字管理してしまった方がシンプルで分かりやすいということですね。

continue
window_count[c] += 1
if window_count[c] == t_count[c]:
match_num += 1
while left <= right and match_num == len(t_count):
if right - left + 1 < min_window_index[1] - min_window_index[0] + 1:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ 1 を外しても良いかなと思いました。ただ、長さを表現するのであれば + 1 のほうが妥当なため、どちらが良いか判断できませんでした。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

長さを利用している部分も他にないので冗長な表現だなと考え直し、+1は不要な気がしてきました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultdict() の CPython の実装を考えるとよいと思います。 defaultdict はハッシュテーブルで実装されていると思います。 (間違っているかもしれないので調べていただけるとありがたいです。) ハッシュテーブルは、ある値にアクセスする際、はじめにキーのハッシュを調べます。続いてどのバケットに値が入っているか、インデックスを計算します。次にインデックスに基づいて、そのバケットにアクセスします。このアクセスはランダムアクセスとなります。そしてバケットから値への参照を用いて、値にアクセスします。このときにもう 1 回ランダムアクセスが発生します。このため、都合 2 回ランダムアクセスが発生します。
一方、 list で実装した場合、インデックスを用いたランダムアクセスが 1 回のみ発生します。これらにより、 list のほうが定数倍早くなる可能性があります。

ただし、 Python のようなインタープリター言語では、上記は誤差になる、または諸々の理由により逆転する可能性があります。パフォーマンスに sensitive な場合は、上記まで考え、かつマイクロベンチマークをとり、比較検討するとよいと思います。そもそもパフォーマンスに sensitive な処理を Python で書くのが間違っている気もします。自分なら C++ で書くと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
よく分かりました。

CPythonのソースこちらのサイトからハッシュテーブルが利用されていることが確認できました。
defaultdictはdictを利用しているだけでしたので、dictの実装を確認しました。

min_window_index = (left, right)
if s[left] in t_count:
window_count[s[left]] -= 1
if window_count[s[left]] < t_count[s[left]]:
match_num -= 1
left += 1
min_window_left, min_window_right = min_window_index
if min_window_right == math.inf:
return ""
return s[min_window_left : min_window_right + 1]
27 changes: 27 additions & 0 deletions grind75_hard/01_76_Minimum Window Substring/level_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_count = defaultdict(int)
for c in t:
t_count[c] += 1
left = 0
min_window_index = (left, math.inf)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私なら、math.inf ではなくて、len(s) にするかもしれません。趣味の範囲です。

window_count = defaultdict(int)
match_num = 0
for right, c in enumerate(s):
if c not in t_count:
continue
window_count[c] += 1
if window_count[c] == t_count[c]:
match_num += 1
while left <= right and match_num == len(t_count):
if right - left + 1 < min_window_index[1] - min_window_index[0] + 1:
min_window_index = (left, right)
if s[left] in t_count:
window_count[s[left]] -= 1
if window_count[s[left]] < t_count[s[left]]:
match_num -= 1
left += 1
left, right = min_window_index
if right == math.inf:
return ""
return s[left : right + 1]
28 changes: 28 additions & 0 deletions grind75_hard/01_76_Minimum Window Substring/level_4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 変数名の修正
# defaultdectを[0]*128に変更
# tに含まれていない文字もカウントするように修正
class Solution:
def minWindow(self, s: str, t: str) -> str:
num_chars_in_t = [0] * 128
for c in t:
num_chars_in_t[ord(c)] += 1
num_needed_matches = 128 - num_chars_in_t.count(0)
left = 0
num_matches = 0
num_chars_in_window = [0] * 128
min_window_index = (left, len(s))
for right, c in enumerate(s):
num_chars_in_window[ord(c)] += 1
if num_chars_in_window[ord(c)] == num_chars_in_t[ord(c)]:
num_matches += 1
while left <= right and num_matches == num_needed_matches:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left <= rightが不要です。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おっしゃるとおりです。
windowの幅が最低文字数を下回ったら絶対にwhileを抜けるので、left,rightの位置関係はここで考える必要ないですね。

if right - left + 1 < min_window_index[1] - min_window_index[0] + 1:
min_window_index = (left, right)
num_chars_in_window[ord(s[left])] -= 1
if num_chars_in_window[ord(s[left])] < num_chars_in_t[ord(s[left])]:
num_matches -= 1
left += 1
min_window_left, min_window_right = min_window_index
if min_window_right == len(s):
return ""
return s[min_window_left : min_window_right + 1]