-
Notifications
You must be signed in to change notification settings - Fork 0
Minimum Window Substring #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # 76. Minimum Window Substring | ||
|
|
||
| ## step1 | ||
| 32mほどで解いた。sliding windowを使えばO(n)で解けることにはすぐに気がついたが、処理をコードに変換するのに時間がかかった。最初の解法: step1.py | ||
|
|
||
| おそらくこの問題の解法は sliding window を用いた用いたこの解法しかないのでは。あとは同じ解法で以下にコードを改善するか。 | ||
|
|
||
|
|
||
| ## 他の人のコード | ||
| https://github.com/hayashi-ay/leetcode/pull/73 | ||
|
|
||
| > 工夫すると毎回全ての文字のカウントを確認しなくて済むような方法があります。 | ||
|
|
||
| たしかに最も単純なのは毎回全ての文字のカウントを確認する方法になるのか。 | ||
|
|
||
| 変数名はleft, rightがわかりやすい。 | ||
| 書き方はright の for 文で回した方がわかりやすいな | ||
|
|
||
| https://github.com/shining-ai/leetcode/pull/61 | ||
| 条件を num_matches で管理するのはわかりやすい | ||
|
|
||
| 他に ord を用いて配列をdictの代わりに用いる方法がある | ||
|
|
||
| https://github.com/potrue/leetcode/pull/67/changes | ||
|
|
||
| ## step2 | ||
| for 文で書き直す。他に、クラスを使ってwindow_infoを定義する工夫をした(大袈裟かもしれない)。 | ||
|
|
||
| ## step3 | ||
| 書く | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import collections | ||
|
|
||
|
|
||
| class Solution: | ||
| def minWindow(self, s: str, t: str) -> str: | ||
| counter_t = collections.Counter(t) | ||
| counter_window = collections.Counter(s) | ||
| for key, count in counter_t.items(): | ||
| if counter_window.get(key, 0) < count: | ||
| return "" | ||
|
|
||
| first = 0 | ||
| while s[first] not in counter_t: | ||
| first += 1 | ||
|
|
||
| last = len(s) - 1 | ||
| while first < last: | ||
| while s[last] not in counter_t: | ||
| last -= 1 | ||
| if counter_t[s[last]] >= counter_window[s[last]]: | ||
| break | ||
| counter_window[s[last]] -= 1 | ||
| last -= 1 | ||
|
|
||
| min_window_first = first | ||
| min_window_last = last | ||
| min_window_size = last - first + 1 | ||
| while first < len(s) - 1: | ||
| if counter_window[s[first]] <= counter_t[s[first]]: | ||
| next_last = last + 1 | ||
| while next_last < len(s) and s[next_last] != s[first]: | ||
| counter_window[s[next_last]] += 1 | ||
| next_last += 1 | ||
| if next_last == len(s): | ||
| break | ||
| last = next_last | ||
| else: | ||
| counter_window[s[first]] -= 1 | ||
|
|
||
| first += 1 | ||
| while s[first] not in counter_t: | ||
| first += 1 | ||
| if min_window_size > last - first + 1: | ||
| min_window_size = last - first + 1 | ||
| min_window_first = first | ||
| min_window_last = last | ||
|
|
||
| return s[min_window_first : min_window_last + 1] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import collections | ||
|
|
||
|
|
||
| class Solution: | ||
| def minWindow(self, s: str, t: str) -> str: | ||
| counter_t = collections.Counter(t) | ||
| counter_window = collections.defaultdict(int) | ||
| for c in s: | ||
| if c in counter_t: | ||
| counter_window[c] += 1 | ||
| for key, count in counter_t.items(): | ||
| if counter_window.get(key, 0) < count: | ||
| return "" | ||
|
|
||
| first = 0 | ||
| while s[first] not in counter_t: | ||
| first += 1 | ||
|
|
||
| last = len(s) - 1 | ||
| while first < last: | ||
| while s[last] not in counter_t: | ||
| last -= 1 | ||
| if counter_t[s[last]] >= counter_window[s[last]]: | ||
| break | ||
| counter_window[s[last]] -= 1 | ||
| last -= 1 | ||
|
|
||
| min_window_first = first | ||
| min_window_last = last | ||
| min_window_size = last - first + 1 | ||
| while first < len(s) - 1: | ||
| if counter_window[s[first]] <= counter_t[s[first]]: | ||
| next_last = last + 1 | ||
| while next_last < len(s) and s[next_last] != s[first]: | ||
| counter_window[s[next_last]] += 1 | ||
| next_last += 1 | ||
| if next_last == len(s): | ||
| break | ||
| last = next_last | ||
| else: | ||
| counter_window[s[first]] -= 1 | ||
|
|
||
| first += 1 | ||
| while s[first] not in counter_t: | ||
| first += 1 | ||
| if min_window_size > last - first + 1: | ||
| min_window_size = last - first + 1 | ||
| min_window_first = first | ||
| min_window_last = last | ||
|
|
||
| return s[min_window_first : min_window_last + 1] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import collections | ||
| import math | ||
|
|
||
|
|
||
| class WindowInfo: | ||
| left = None | ||
| right = None | ||
| size = math.inf | ||
|
|
||
|
|
||
| class Solution: | ||
| def minWindow(self, s: str, t: str) -> str: | ||
| required_count = collections.Counter(t) | ||
| window_count = collections.defaultdict(int) | ||
| num_required_unique_chars = len(required_count) | ||
| num_formed_chars = 0 | ||
|
|
||
| window_info = WindowInfo() | ||
| left = 0 | ||
| for right in range(len(s)): | ||
| window_count[s[right]] += 1 | ||
| if window_count[s[right]] == required_count.get(s[right], -1): | ||
| num_formed_chars += 1 | ||
|
|
||
| while num_formed_chars == num_required_unique_chars: | ||
| if window_info.size > right - left + 1: | ||
| window_info.size = right - left + 1 | ||
| window_info.left = left | ||
| window_info.right = right | ||
|
|
||
| window_count[s[left]] -= 1 | ||
| if window_count[s[left]] < required_count.get(s[left], -1): | ||
| num_formed_chars -= 1 | ||
| left += 1 | ||
|
|
||
| if math.isinf(window_info.size): | ||
| return "" | ||
| return s[window_info.left : window_info.right + 1] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import collections | ||
| import math | ||
|
|
||
|
|
||
| class WindowInfo: | ||
| left = None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. クラスにするのであれば、left: Optional[int]とかにしたほうがいいかなと思います。なんでも入るべきではないでしょう。 |
||
| right = None | ||
| size = math.inf | ||
|
|
||
|
|
||
| class Solution: | ||
| def minWindow(self, s: str, t: str) -> str: | ||
| required_count = collections.Counter(t) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. required_countとwindow_countを両方使っていますが、一つにまとめて正負を反対にすると、すっきりするかなと思います。テクニックとしてはArai60の問題にもいくつか出てきますね。 import collections
import math
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = collections.Counter(t)
missing = len(t)
left = 0
best_left = 0
best_size = math.inf
for right, char in enumerate(s):
if need[char] > 0:
missing -= 1
need[char] -= 1
while missing == 0:
if right - left + 1 < best_size:
best_left = left
best_size = right - left + 1
left_char = s[left]
need[left_char] += 1
if need[left_char] > 0:
missing += 1
left += 1
return "" if math.isinf(best_size) else s[best_left : best_left + best_size]There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hayashi-ay/leetcode#73
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 二つの変数で管理する必要はないですね。最初の step1 で dict 二つで管理していたのに引きずられました。 |
||
| window_count = collections.defaultdict(int) | ||
| num_required_count = len(required_count) | ||
| num_formed_count = 0 | ||
| window_info = WindowInfo() | ||
|
|
||
| left = 0 | ||
| for right in range(len(s)): | ||
| window_count[s[right]] += 1 | ||
| if window_count[s[right]] == required_count.get(s[right], -1): | ||
| num_formed_count += 1 | ||
|
|
||
| while num_formed_count == num_required_count: | ||
| if window_info.size > right - left + 1: | ||
| window_info.left = left | ||
| window_info.right = right | ||
| window_info.size = right - left + 1 | ||
|
|
||
| window_count[s[left]] -= 1 | ||
| if window_count[s[left]] < required_count.get(s[left], -1): | ||
| num_formed_count -= 1 | ||
| left += 1 | ||
|
|
||
| return ( | ||
| "" | ||
| if math.isinf(window_info.size) | ||
| else s[window_info.left : window_info.right + 1] | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import collections | ||
|
|
||
|
|
||
| class WindowInfo: | ||
| def __init__(self) -> None: | ||
| self.left: int | None = None | ||
| self.right: int | None = None | ||
|
|
||
| def update(self, left: int, right: int) -> None: | ||
| self.left = left | ||
| self.right = right | ||
|
|
||
| @property | ||
| def size(self) -> int | None: | ||
| if self.left is None or self.right is None: | ||
| return None | ||
| return self.right - self.left + 1 | ||
|
|
||
|
|
||
| class Solution: | ||
| def minWindow(self, s: str, t: str) -> str: | ||
| required_count = collections.Counter(t) | ||
| window_count = collections.defaultdict(int) | ||
| num_required_count = len(required_count) | ||
| window_info = WindowInfo() | ||
|
|
||
| left = 0 | ||
| for right in range(len(s)): | ||
| window_count[s[right]] += 1 | ||
| if window_count[s[right]] == required_count.get(s[right], -1): | ||
| num_required_count -= 1 | ||
|
|
||
| while num_required_count == 0: | ||
| if window_info.size is None or window_info.size > right - left + 1: | ||
| window_info.update(left, right) | ||
|
|
||
| window_count[s[left]] -= 1 | ||
| if window_count[s[left]] < required_count.get(s[left], -1): | ||
| num_required_count += 1 | ||
| left += 1 | ||
|
|
||
| return ( | ||
| "" | ||
| if window_info.size is None | ||
| else s[window_info.left : window_info.right + 1] | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このクラスは必要でしょうか?
また、もしクラスにするなら、sizeはleft/rightが変わったときに自動計算するようにしないと、クラス自体が不変条件を満たすように自動的に振る舞ってくれないので、利用する側が不変条件を満たすこと(sizeがright-left+1になること)を注意しないといけないので、単に変数を羅列しているのと変わらないかなと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご指摘の通りだと思います。updateメソッドを定義して更新をカプセル化しました(step3_revised.py)。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そもそもsizeをメンバー変数として保存せず、その場で計算すると良いと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C++ならstring_viewで良さそうですね。Pythonの標準ライブラリで相当するビューオブジェクトはないかもしれません。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかに@Propertyよりもメンバ変数の方が適切ですね。
C++のstring_viewはコピーをせずに覗き見ができるのですね。
https://cpprefjp.github.io/reference/string_view.html