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 0076.Minimum-Window-Substring/memo.md
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
書く

48 changes: 48 additions & 0 deletions 0076.Minimum-Window-Substring/step1.py
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]
51 changes: 51 additions & 0 deletions 0076.Minimum-Window-Substring/step1_revised.py
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]
38 changes: 38 additions & 0 deletions 0076.Minimum-Window-Substring/step2.py
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]
40 changes: 40 additions & 0 deletions 0076.Minimum-Window-Substring/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import collections
import math


class WindowInfo:

Copy link
Copy Markdown

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になること)を注意しないといけないので、単に変数を羅列しているのと変わらないかなと思います。

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.

ご指摘の通りだと思います。updateメソッドを定義して更新をカプセル化しました(step3_revised.py)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

そもそもsizeをメンバー変数として保存せず、その場で計算すると良いと思います。

class WindowInfo:
    def __init__(self) -> None:
        self.left: int | None = None
        self.right: int | None = None

    @property
    def size(self) -> int | float:
        if self.left is None or self.right is None:
            return math.inf
        return self.right - self.left + 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.

C++ならstring_viewで良さそうですね。Pythonの標準ライブラリで相当するビューオブジェクトはないかもしれません。

class Solution {
public:
    string minWindow(string s, string t) {
        int cnt[128] = {};
        for (char c : t) {
            ++cnt[c];
        }
        int unused = t.size();
        string_view min_win;
        int l = 0;
        for (int r = 0; r < s.size(); ++r) {
            if (--cnt[s[r]] >= 0) {
                unused -= 1;
            }
            while (unused == 0) {
                if (min_win.empty() || r - l + 1 < min_win.size()) {
                    min_win = string_view(s.begin() + l, s.begin() + r + 1);
                }
                if (++cnt[s[l++]] > 0) {
                    ++unused;
                }
            }
        }
        return string(min_win);
    }
};

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.

たしかに@Propertyよりもメンバ変数の方が適切ですね。

C++のstring_viewはコピーをせずに覗き見ができるのですね。
https://cpprefjp.github.io/reference/string_view.html

left = None

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: Optional[int]とかにしたほうがいいかなと思います。なんでも入るべきではないでしょう。

right = None
size = math.inf


class Solution:
def minWindow(self, s: str, t: str) -> str:
required_count = collections.Counter(t)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

required_countとwindow_countを両方使っていますが、一つにまとめて正負を反対にすると、すっきりするかなと思います。テクニックとしてはArai60の問題にもいくつか出てきますね。
ChatGPTによるサンプル:

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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hayashi-ay/leetcode#73
さんの 3rd が本質的に同様ですかね。

@tom4649 tom4649 Jun 4, 2026

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.

二つの変数で管理する必要はないですね。最初の 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]
)
46 changes: 46 additions & 0 deletions 0076.Minimum-Window-Substring/step3_revised.py
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]
)