From e48ba04dffb34f38f26f6364ad323b6534fc5b8c Mon Sep 17 00:00:00 2001 From: sakzk Date: Mon, 6 May 2024 04:51:05 +0900 Subject: [PATCH 1/9] Create 3.md --- 3.md | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 3.md diff --git a/3.md b/3.md new file mode 100644 index 0000000..50c83ba --- /dev/null +++ b/3.md @@ -0,0 +1,193 @@ +# 参考 +- https://github.com/thonda28/leetcode/pull/6/files#diff-6b92fcf16b9cd27d1d2d7ff3daeb4a7f48fb0a4fd64c5c68c2a58ae7af4a1837 + - dictの命名は `x_to_y` もよさそう + - inclusive, exclusiveを含意している変数名がある + - 閉区間:start, end, first, last, begin, finish + - 開区間:lower_bound, upper_bound, limit + - forでrightを進めていく実装。重複したら、被った文字の次の文字にleftをセットする。 + - すでに出現した文字に対してそのインデックスを覚えておく。 + - `longest_lengt = max(right, char_to_index[right] + 1)` で max() はleftを逆戻りさせないために必要。"abba"のように重複が挟まれていると、間の重複を飛び越してしまう。 + - こちらも `for right in s:` で文字を走査し、重複のたびにleftを更新する実装 https://github.com/shining-ai/leetcode/pull/48/files#diff-70cb8db03b4879ea63d98354edceeb583ded7f2ca60a22c5d6ad2d53fa853ef5 + +# 調べ物 +辞書関連: +- valueの初期値を設定する方法 + 1. 書く. `if x not in my_dict: my_dict[x] = default_value` で初期化する + 2. `my_dict[key] = setdefault(value, default_value)` を使う。 https://docs.python.org/3/library/stdtypes.html#dict.setdefault + 3. collectonsモジュールの`defaultdict(<型>)`を使う https://docs.python.org/3/library/collections.html#defaultdict-objects + - このときに指定する型を `default_factory` と呼ぶ。setdefaultより早いらしい。 +- 実装 + - dictの実装。8000行あって怯む。https://github.com/python/cpython/blob/main/Objects/dictobject.c + - 概略の解説 https://stackoverflow.com/questions/327311/how-are-pythons-built-in-dictionaries-implemented/44509302#44509302 + - 3.6以前と以後で大きく変わっている。 + - 3.7から仕様として挿入順を保つようになった + - ハッシュの衝突はオープンアドレス法で処理される https://en.wikipedia.org/wiki/Hash_table#Open_addressing + - 要素の数が増えるとスロット数が拡張される + - 衝突を防ぐために要素数より大きい領域が確保されている。 + +# 問題 +題意:与えられた文字列にたいして、異なる文字だけからなる部分文字列の最大の長さはいくらか。 + +# 全探索 step1 +すべての区間に対して文字の出現回数を調べ、同じ文字が出現しない区間の長さを最大長と比較し更新していく。 + +計算量: +- 時間 O(n^3) 二重ループの中で配列の走査を2回呼んでいる O(n^2 * (n + n))。TLE +- 空間 O(n^3) O(n)のdictをn^2個構築している + - 入力の文字列長をnとして、substringの文字数の総和は、1文字がn個、2文字がn-1個、3文字がn-2個、、、n文字が1個。長い文字列ほど少ないので O(n^3) は上限ではない + +```py +from collections import defaultdict + +class Solution: + def count_chars(self, s:str): + char_count_map = defaultdict(int) + for char in s: + char_count_map[char] += 1 + return char_count_map + + def nums_appear_once(self, nums: List[int]): + for num in nums: + if num > 1: + return False + return True + + def lengthOfLongestSubstring(self, s: str) -> int: + max_substring_length = 0 + i = 0 + while i <= len(s) - 1: + j = i + while j <= len(s) - 1: + char_count_map = self.count_chars(s[i : j+1]) + current_substring_length = 0 + current_substring_length = j - i + 1 + max_substring_length = max(max_substring_length, current_substring_length) + j += 1 + i += 1 + return max_substring_length +# 考えるのに5分、全体の実装に8分、閉区間で書き直し&関数名で悩み、42分で動くものが完成 (大きい入力ではTLE) +``` +コードに対する感覚: +- 制御:二重ループの中身がごちゃごちゃしている。辞書を作って、それをさらに走査するのは、やりたいことに対して大げさに感じる。 +- 命名:nums_appear_once()が変。booleanを返すので疑問形になっていてほしい。 +- 制御・初期値:閉区間で書くためにwhileを使ったせいでインデックスの更新が散らばってしまっている。 + + +# 全探索 step2 +```py +class Solution: + def is_valid_substring(self, s: str): + char_count_map = defaultdict(int) + for char in s: + char_count_map[char] += 1 + if char_count_map[char] > 1: + return False + return True + + def lengthOfLongestSubstring(self, s: str) -> int: + max_substring_length = 0 + for i in range(len(s)): + for j in range(i, len(s)): + ## 説明変数 current_substring_lengthを使用 + current_substring_length = 0 + if self.is_valid_substring(s[i : j+1]): + current_substring_length = j - i + 1 + max_substring_length = max(max_substring_length, + current_substring_length) + + ## 上記ブロックの異なる表現 + if self.is_valid_substring(s[i : j+1]): + max_substring_length = max(j - i + 1, max_substring_length) # `j - i + 1` is the length of s[i:j+1] + return max_substring_length +``` +コードに対する感覚: +- 説明変数として current_substring_lengthを使っているが、max()の中で直接 `j-i-1`を使って、コメントで補うほうがスッキリする。 +- 印象として全探索step1より整理されているように感じる。 + + +# setを使う解法 step1 +注目している部分文字列が含む文字をsetで記憶 + +計算量: +- 時間 O(n) left, rightともにsの末尾近くまで進むとき最悪でO(2n)、すべての文字列が異なるとき最善。 +- 空間 O(n) すべての文字列が異なるとき最悪、すべての文字が同じ時最善。 + +```py +def lengthOfLongestSubstring(self, s: str) -> int: + max_substring_length = 0 + seen_chars = set() + left = 0 + right = 0 + while left < len(s): + while right < len(s) and s[right] not in seen_chars: + seen_chars.add(s[right]) + right += 1 + max_substring_length = max(right - left, max_substring_length) + seen_chars.remove(s[left]) + left += 1 + return max_substring_length +``` +アプローチ: +- 全探索ではスライスが更新されるごとに辞書を作り直していたが、right, leftのインクリメントにもとなって端の移動分だけ更新すると「現時点のsubstringに含まれている文字がなにか」の情報を使い回せる。 + +コードに対する感覚: +- 参考にした回答にはfor文でrightをインクリメントする回答が多かった。leftに対するwhile文で一番外側の制御を書くのは自分以外には読みにくいかもしれない。 + - 2重ループっぽい見た目なので O(n^2)っぽい印象を与えるかもしれない + - 「重複要素を見つけた直後にleftを1だけインクリメントする」というのは、ぱっと見では、leftを進め足りないと思ってしまう。 + - 重複が見つかったときのループの動き:`while right < ~`のループに入らずに、seen_charsがs[right]と重複するs[left]を吐き出すまで、leftをインクリメントし続ける。 + - お尻を一歩引きつけるごとに頭を出しては引っ込める尺取り虫 + +変数名の改善: +- left, rightだと、`right - left`の順序で何度も間違える。 +- leftの代替として、substring_start_index, substring_head_index, first_char_indexなど。 + +# setを使う解法 step2 + +やったこと: +- 変数名の変更 `max_substring_leng` -> `max_length` + +```py +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + seen_chars = set() + max_length = 0 + left = 0 + right = 0 + while left < len(s): + while right < len(s) and s[right] not in seen_chars: + seen_chars.add(s[right]) + right += 1 + max_length = max(right - left, max_length) + seen_chars.remove(s[left]) + left += 1 + return max_length +``` +コードに対する感覚: +- `max_length`ではなく、問題文の表現を使った `longest_substring_length` のほうがよさそう。 + - 抽象的には、既存の言葉づかいと一貫性した表現にすること。 +- max_lengthは略しすぎになる恐れがありそう。関数名を脳内に保持しておかなければならないので、関数が長くなったらsubstringを省略しないほうがよいと思われる。 + - 関数名を忘れた読み手には、スライスを使っている箇所から max_length は、substring の長さを言っていると予想させることになる。 + - 関数が長くなることに備えるならばsubstringをはじめから省略しないことを選択する +- `max_X = max(current_X, max_X)` では、`max()` の引数は、字面が長いほうを後ろに置くほうが見やすそう。 + +# set step3 +```py +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + # 4'15 -> 5'25 -> 4'01 + longest_length = 0 + seen_chars = set() + base_index = 0 + right = 0 + while base_index < len(s): + while right < len(s) and s[right] not in seen_chars: + seen_chars.add(s[right]) + right += 1 + longest_length = max(right - base_index, longest_length) + seen_chars.remove(s[base_index]) + base_index += 1 + return longest_length +``` + +コードに対する感覚: +- base_indexは良い名前ではないと感じる。substring_start_indexがしっくりくる。 From 3d93ed936f50790ffb223764206b7e2ac0ad86f3 Mon Sep 17 00:00:00 2001 From: sakzk Date: Mon, 6 May 2024 04:51:55 +0900 Subject: [PATCH 2/9] Update 3.md --- 3.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/3.md b/3.md index 50c83ba..c903cb2 100644 --- a/3.md +++ b/3.md @@ -1,30 +1,3 @@ -# 参考 -- https://github.com/thonda28/leetcode/pull/6/files#diff-6b92fcf16b9cd27d1d2d7ff3daeb4a7f48fb0a4fd64c5c68c2a58ae7af4a1837 - - dictの命名は `x_to_y` もよさそう - - inclusive, exclusiveを含意している変数名がある - - 閉区間:start, end, first, last, begin, finish - - 開区間:lower_bound, upper_bound, limit - - forでrightを進めていく実装。重複したら、被った文字の次の文字にleftをセットする。 - - すでに出現した文字に対してそのインデックスを覚えておく。 - - `longest_lengt = max(right, char_to_index[right] + 1)` で max() はleftを逆戻りさせないために必要。"abba"のように重複が挟まれていると、間の重複を飛び越してしまう。 - - こちらも `for right in s:` で文字を走査し、重複のたびにleftを更新する実装 https://github.com/shining-ai/leetcode/pull/48/files#diff-70cb8db03b4879ea63d98354edceeb583ded7f2ca60a22c5d6ad2d53fa853ef5 - -# 調べ物 -辞書関連: -- valueの初期値を設定する方法 - 1. 書く. `if x not in my_dict: my_dict[x] = default_value` で初期化する - 2. `my_dict[key] = setdefault(value, default_value)` を使う。 https://docs.python.org/3/library/stdtypes.html#dict.setdefault - 3. collectonsモジュールの`defaultdict(<型>)`を使う https://docs.python.org/3/library/collections.html#defaultdict-objects - - このときに指定する型を `default_factory` と呼ぶ。setdefaultより早いらしい。 -- 実装 - - dictの実装。8000行あって怯む。https://github.com/python/cpython/blob/main/Objects/dictobject.c - - 概略の解説 https://stackoverflow.com/questions/327311/how-are-pythons-built-in-dictionaries-implemented/44509302#44509302 - - 3.6以前と以後で大きく変わっている。 - - 3.7から仕様として挿入順を保つようになった - - ハッシュの衝突はオープンアドレス法で処理される https://en.wikipedia.org/wiki/Hash_table#Open_addressing - - 要素の数が増えるとスロット数が拡張される - - 衝突を防ぐために要素数より大きい領域が確保されている。 - # 問題 題意:与えられた文字列にたいして、異なる文字だけからなる部分文字列の最大の長さはいくらか。 @@ -191,3 +164,30 @@ class Solution: コードに対する感覚: - base_indexは良い名前ではないと感じる。substring_start_indexがしっくりくる。 + +# 参考 +- https://github.com/thonda28/leetcode/pull/6/files#diff-6b92fcf16b9cd27d1d2d7ff3daeb4a7f48fb0a4fd64c5c68c2a58ae7af4a1837 + - dictの命名は `x_to_y` もよさそう + - inclusive, exclusiveを含意している変数名がある + - 閉区間:start, end, first, last, begin, finish + - 開区間:lower_bound, upper_bound, limit + - forでrightを進めていく実装。重複したら、被った文字の次の文字にleftをセットする。 + - すでに出現した文字に対してそのインデックスを覚えておく。 + - `longest_lengt = max(right, char_to_index[right] + 1)` で max() はleftを逆戻りさせないために必要。"abba"のように重複が挟まれていると、間の重複を飛び越してしまう。 + - こちらも `for right in s:` で文字を走査し、重複のたびにleftを更新する実装 https://github.com/shining-ai/leetcode/pull/48/files#diff-70cb8db03b4879ea63d98354edceeb583ded7f2ca60a22c5d6ad2d53fa853ef5 + +# 調べ物 +辞書関連: +- valueの初期値を設定する方法 + 1. 書く. `if x not in my_dict: my_dict[x] = default_value` で初期化する + 2. `my_dict[key] = setdefault(value, default_value)` を使う。 https://docs.python.org/3/library/stdtypes.html#dict.setdefault + 3. collectonsモジュールの`defaultdict(<型>)`を使う https://docs.python.org/3/library/collections.html#defaultdict-objects + - このときに指定する型を `default_factory` と呼ぶ。setdefaultより早いらしい。 +- 実装 + - dictの実装。8000行あって怯む。https://github.com/python/cpython/blob/main/Objects/dictobject.c + - 概略の解説 https://stackoverflow.com/questions/327311/how-are-pythons-built-in-dictionaries-implemented/44509302#44509302 + - 3.6以前と以後で大きく変わっている。 + - 3.7から仕様として挿入順を保つようになった + - ハッシュの衝突はオープンアドレス法で処理される https://en.wikipedia.org/wiki/Hash_table#Open_addressing + - 要素の数が増えるとスロット数が拡張される + - 衝突を防ぐために要素数より大きい領域が確保されている。 From f0a5fe369ff29ab3ae1d53dff975fadc37280d7b Mon Sep 17 00:00:00 2001 From: sakzk Date: Thu, 9 May 2024 20:51:53 +0900 Subject: [PATCH 3/9] 20 valid parentheses --- 20_1.py | 37 ++++++++++++++++++++++++++++++++++ 20_2.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 20_3.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 20_note.md | 25 +++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 20_1.py create mode 100644 20_2.py create mode 100644 20_3.py create mode 100644 20_note.md diff --git a/20_1.py b/20_1.py new file mode 100644 index 0000000..2924982 --- /dev/null +++ b/20_1.py @@ -0,0 +1,37 @@ +# step1 + +class Solution: + def isValid(self, s: str) -> bool: + parentheses_stack = [] + close_to_open = {")":"(", "]":"[", "}":"{"} + for char in s: + if char not in "()[]{}": + continue + if char in "([{": + parentheses_stack.append(char) + continue + if char in ")]}": + if not parentheses_stack: + return False + if parentheses_stack.pop() != close_to_open[char]: + return False + if parentheses_stack: + return False + return True + +""" +カッコの対応づけ方の選択。 +思いついた選択肢と判断理由は次の通り。 +1. すべて書き下す if char == "(", if char == ")"... は分岐の数が増えるので大変そう +2. open_parentheses = "([{", close_parentheses = ")]}" はインデックスを経由するので対応付けが曖昧、カッコ以外にも管理したい組が増えると大変になってきそう。 +3. keyに右括弧, valueに左括弧を格納するdictを使う。 + 1. 条件文でdict.keys(), dict.values() を都度計算するのはもったいなく感じるので "([{" と書き下した + 2. 組が増えた場合は、ループに入るまえに dict.keys(), dict.values() を求めてそれぞれを set() に入れて, `if char in close_X` などとして対応できそう。 +と考えて、辞書を選択した。 + +return Falseを実行する条件分を2つに分解した。まとめて書くと80列をオーバーしたため。 +PEP8によれば、次のように書いてもいい +if (not parentheses_stack or \ + parentheses_stack.pop() != close_to_open[char]): + return False +""" diff --git a/20_2.py b/20_2.py new file mode 100644 index 0000000..fab76d6 --- /dev/null +++ b/20_2.py @@ -0,0 +1,59 @@ +# step2−1 + +class Solution: + def isValid(self, s: str) -> bool: + parentheses_stack = [] + close_to_open = {")":"(", "]":"[", "}":"{"} + for char in s: + if char not in "()[]{}": + pass + elif char in "([{": + parentheses_stack.append(char) + else:# iff char in ")]}": + if not parentheses_stack or parentheses_stack.pop() != close_to_open[char]: + return False + if parentheses_stack: + return False + return True + +""" +修正点: +- if-continueが3つ並んでいたのを if-elif-elseに書き換えた + - 3つの条件文が排反なので書き換えてみたが、elseを使うとむしろ排反であることが伝わりにくいと感じた。 +""" + +# step2-2 +class Solution: + def isValid(self, s: str) -> bool: + parentheses_stack = [] + close_to_open = {")":"(", "]":"[", "}":"{"} + open_brackets = set(close_to_open.values()) + close_brackets = set(close_to_open.keys()) + brackets = set() + for close, open_ in close_to_open.items(): + brackets.add(close) + brackets.add(_open) + + for char in s: + if char not in brackets: + pass + elif char in open_brackets: + parentheses_stack.append(char) + else: # iff char in open_brackets + if not parentheses_stack or parentheses_stack.pop() != close_to_open[char]: + return False + if parentheses_stack: + return False + return True +""" +機能の拡張 +- `char in "文字列リテラル"` を書き換えた。 + - close_to_open の要素の key, value が2文字以上で構成される場合にも対応するため。 + - `in` の存在判定は、要素数が少ないとき、set() ではなく list() のほうが早いはず。 + - set() はハッシュの計算を行い、list()は線形探索を行うため + +変数名: +- `for close, open_ in close_to_open.items():` + - close, open_ は for 文でしか使わないので open_bracket を省略して open にしてみたが、組み込み関数の open()と被るので、アンダースコアをつけた。 + - 参照:https://peps.python.org/pep-0008/#descriptive-naming-styles +""" diff --git a/20_3.py b/20_3.py new file mode 100644 index 0000000..fae9a80 --- /dev/null +++ b/20_3.py @@ -0,0 +1,53 @@ +# step3 +# 6'30 +from collections import deque +class Solution: + def isValid(self, s: str) -> bool: + brackets_stack = deque() + close_to_open = {")":"(", "]":"[", "}":"{"} + open_bracket_chars = set(close_to_open.values()) + close_bracket_chars = set(close_to_open.keys()) + all_bracket_chars = set() + for close, open_ in close_to_open.items(): + all_bracket_chars.add(close) + all_bracket_chars.add(_open) + + for char in s: + if char not in all_bracket_chars: + continue + if char in open_bracket_chars: + brackets_stack.append(char) + continue + if char in close_bracket_chars: + if not brackets_stack: + return False + if brackets_stack.pop != close_to_open[char]: + return False + return not brackets_stack + +""" +データ構造の変更: +スタックとして deque (double ended queue) を使う。 +stackの頂点の要素を取り出さずに、インデックスで見るだけなら以下のようにする。 + ``` + peek = brackets_stack[-1] + if peek != close_to_open[char]: + return False + brackets_stack.pop() + ``` + +変数名の変更: +parentheses -> brackets: カッコの総称としては、parentheses よりも brackets のほうが良さそうだったため。 (参照) https://en.wikipedia.org/wiki/Bracket + +返り値の変更: + ``` + if not brackets_stack: + return False + return True + ``` +は + ``` + return not brackets_stack + ``` +だけでいい。 +""" diff --git a/20_note.md b/20_note.md new file mode 100644 index 0000000..e350d62 --- /dev/null +++ b/20_note.md @@ -0,0 +1,25 @@ +想定していない入力に対する処理: +https://github.com/rm3as/code_interview/pull/4/files +https://discord.com/channels/1084280443945353267/1225849404037009609/1231649668442882109 + +命名関連: +予約語は変数名に使わない:https://docs.python.org/3/reference/lexical_analysis.html#keywords +予約語との被りを避けるための 変数名末尾の`_` :https://peps.python.org/pep-0008/#descriptive-naming-styles + google python style guide には 同様の使い方について言及が見当たらなかった https://google.github.io/styleguide/ +カッコの名前 (parentheses と brackets で迷ったら) :https://en.wikipedia.org/wiki/Bracket + +dictionary関連: +keys(), values(), items()による要素の取り出しについて。これらのメソッドの結果には `in`が使える。reversed が使えるのは3.8から。dict_itemsは組をタプルで包んだもの。開いたものではない。:https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects +items()が [key1, value1, key2, value2...] を作ると思いこんでデバッグに苦労した。 + +deque関連: +dequeの使い方。rotateは左方向が正。列の先頭が後ろに戻るイメージ。:https://docs.python.org/3/library/collections.html#collections.deque +pythonのdeque は doubley-linked list。ただし、要素一つ一つがノードなのではなく、固定長のブロック単位でまとまっている。(ブロックあたりのオブジェクトの数は、BLOCKLEN = 64で定義されている) これによりmalloc(), free()の呼び出しが減り、データの局所性も改善する。 https://github.com/python/cpython/blob/1b293b60067f6f4a95984d064ce0f6b6d34c1216/Modules/_collectionsmodule.c#L33 +ahayasiさんの deque コードリーディングのメモ:https://discord.com/channels/1084280443945353267/1200089668901937312/1235931317317799957 +(現状自分は、ソースのドキュメント部分と手持ちの教科書的な知識を照らし合わせてふーんと思った程度。また、サクッと理解できる感じではない。) +先頭要素の編集が多いならlistではなくdequeを使った方がいい。(listは可変長配列なので先頭側の削除、挿入が重い) + +条件文の作り方関連: +演算子の優先順位一覧:https://docs.python.org/3/reference/expressions.html#operator-precedence +条件式内での改行・インデントのフォーマット:https://peps.python.org/pep-0008/#indentation + From 94f63f6bd9c0c948fc635e9c5d8e80654790242d Mon Sep 17 00:00:00 2001 From: sakzk Date: Thu, 9 May 2024 20:55:19 +0900 Subject: [PATCH 4/9] Delete 20_1.py --- 20_1.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 20_1.py diff --git a/20_1.py b/20_1.py deleted file mode 100644 index 2924982..0000000 --- a/20_1.py +++ /dev/null @@ -1,37 +0,0 @@ -# step1 - -class Solution: - def isValid(self, s: str) -> bool: - parentheses_stack = [] - close_to_open = {")":"(", "]":"[", "}":"{"} - for char in s: - if char not in "()[]{}": - continue - if char in "([{": - parentheses_stack.append(char) - continue - if char in ")]}": - if not parentheses_stack: - return False - if parentheses_stack.pop() != close_to_open[char]: - return False - if parentheses_stack: - return False - return True - -""" -カッコの対応づけ方の選択。 -思いついた選択肢と判断理由は次の通り。 -1. すべて書き下す if char == "(", if char == ")"... は分岐の数が増えるので大変そう -2. open_parentheses = "([{", close_parentheses = ")]}" はインデックスを経由するので対応付けが曖昧、カッコ以外にも管理したい組が増えると大変になってきそう。 -3. keyに右括弧, valueに左括弧を格納するdictを使う。 - 1. 条件文でdict.keys(), dict.values() を都度計算するのはもったいなく感じるので "([{" と書き下した - 2. 組が増えた場合は、ループに入るまえに dict.keys(), dict.values() を求めてそれぞれを set() に入れて, `if char in close_X` などとして対応できそう。 -と考えて、辞書を選択した。 - -return Falseを実行する条件分を2つに分解した。まとめて書くと80列をオーバーしたため。 -PEP8によれば、次のように書いてもいい -if (not parentheses_stack or \ - parentheses_stack.pop() != close_to_open[char]): - return False -""" From 44b904d06fd53f6bdb753c4ff324de4a9dba1ada Mon Sep 17 00:00:00 2001 From: sakzk Date: Thu, 9 May 2024 20:55:30 +0900 Subject: [PATCH 5/9] Delete 20_2.py --- 20_2.py | 59 --------------------------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 20_2.py diff --git a/20_2.py b/20_2.py deleted file mode 100644 index fab76d6..0000000 --- a/20_2.py +++ /dev/null @@ -1,59 +0,0 @@ -# step2−1 - -class Solution: - def isValid(self, s: str) -> bool: - parentheses_stack = [] - close_to_open = {")":"(", "]":"[", "}":"{"} - for char in s: - if char not in "()[]{}": - pass - elif char in "([{": - parentheses_stack.append(char) - else:# iff char in ")]}": - if not parentheses_stack or parentheses_stack.pop() != close_to_open[char]: - return False - if parentheses_stack: - return False - return True - -""" -修正点: -- if-continueが3つ並んでいたのを if-elif-elseに書き換えた - - 3つの条件文が排反なので書き換えてみたが、elseを使うとむしろ排反であることが伝わりにくいと感じた。 -""" - -# step2-2 -class Solution: - def isValid(self, s: str) -> bool: - parentheses_stack = [] - close_to_open = {")":"(", "]":"[", "}":"{"} - open_brackets = set(close_to_open.values()) - close_brackets = set(close_to_open.keys()) - brackets = set() - for close, open_ in close_to_open.items(): - brackets.add(close) - brackets.add(_open) - - for char in s: - if char not in brackets: - pass - elif char in open_brackets: - parentheses_stack.append(char) - else: # iff char in open_brackets - if not parentheses_stack or parentheses_stack.pop() != close_to_open[char]: - return False - if parentheses_stack: - return False - return True -""" -機能の拡張 -- `char in "文字列リテラル"` を書き換えた。 - - close_to_open の要素の key, value が2文字以上で構成される場合にも対応するため。 - - `in` の存在判定は、要素数が少ないとき、set() ではなく list() のほうが早いはず。 - - set() はハッシュの計算を行い、list()は線形探索を行うため - -変数名: -- `for close, open_ in close_to_open.items():` - - close, open_ は for 文でしか使わないので open_bracket を省略して open にしてみたが、組み込み関数の open()と被るので、アンダースコアをつけた。 - - 参照:https://peps.python.org/pep-0008/#descriptive-naming-styles -""" From 45dfc3838511f6f81b90b6399491532566b3a377 Mon Sep 17 00:00:00 2001 From: sakzk Date: Thu, 9 May 2024 20:55:38 +0900 Subject: [PATCH 6/9] Delete 20_3.py --- 20_3.py | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 20_3.py diff --git a/20_3.py b/20_3.py deleted file mode 100644 index fae9a80..0000000 --- a/20_3.py +++ /dev/null @@ -1,53 +0,0 @@ -# step3 -# 6'30 -from collections import deque -class Solution: - def isValid(self, s: str) -> bool: - brackets_stack = deque() - close_to_open = {")":"(", "]":"[", "}":"{"} - open_bracket_chars = set(close_to_open.values()) - close_bracket_chars = set(close_to_open.keys()) - all_bracket_chars = set() - for close, open_ in close_to_open.items(): - all_bracket_chars.add(close) - all_bracket_chars.add(_open) - - for char in s: - if char not in all_bracket_chars: - continue - if char in open_bracket_chars: - brackets_stack.append(char) - continue - if char in close_bracket_chars: - if not brackets_stack: - return False - if brackets_stack.pop != close_to_open[char]: - return False - return not brackets_stack - -""" -データ構造の変更: -スタックとして deque (double ended queue) を使う。 -stackの頂点の要素を取り出さずに、インデックスで見るだけなら以下のようにする。 - ``` - peek = brackets_stack[-1] - if peek != close_to_open[char]: - return False - brackets_stack.pop() - ``` - -変数名の変更: -parentheses -> brackets: カッコの総称としては、parentheses よりも brackets のほうが良さそうだったため。 (参照) https://en.wikipedia.org/wiki/Bracket - -返り値の変更: - ``` - if not brackets_stack: - return False - return True - ``` -は - ``` - return not brackets_stack - ``` -だけでいい。 -""" From e0a070905c0d46649d24a92f4675ed6cb2fe4c54 Mon Sep 17 00:00:00 2001 From: sakzk Date: Thu, 9 May 2024 20:55:58 +0900 Subject: [PATCH 7/9] Delete 20_note.md --- 20_note.md | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 20_note.md diff --git a/20_note.md b/20_note.md deleted file mode 100644 index e350d62..0000000 --- a/20_note.md +++ /dev/null @@ -1,25 +0,0 @@ -想定していない入力に対する処理: -https://github.com/rm3as/code_interview/pull/4/files -https://discord.com/channels/1084280443945353267/1225849404037009609/1231649668442882109 - -命名関連: -予約語は変数名に使わない:https://docs.python.org/3/reference/lexical_analysis.html#keywords -予約語との被りを避けるための 変数名末尾の`_` :https://peps.python.org/pep-0008/#descriptive-naming-styles - google python style guide には 同様の使い方について言及が見当たらなかった https://google.github.io/styleguide/ -カッコの名前 (parentheses と brackets で迷ったら) :https://en.wikipedia.org/wiki/Bracket - -dictionary関連: -keys(), values(), items()による要素の取り出しについて。これらのメソッドの結果には `in`が使える。reversed が使えるのは3.8から。dict_itemsは組をタプルで包んだもの。開いたものではない。:https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects -items()が [key1, value1, key2, value2...] を作ると思いこんでデバッグに苦労した。 - -deque関連: -dequeの使い方。rotateは左方向が正。列の先頭が後ろに戻るイメージ。:https://docs.python.org/3/library/collections.html#collections.deque -pythonのdeque は doubley-linked list。ただし、要素一つ一つがノードなのではなく、固定長のブロック単位でまとまっている。(ブロックあたりのオブジェクトの数は、BLOCKLEN = 64で定義されている) これによりmalloc(), free()の呼び出しが減り、データの局所性も改善する。 https://github.com/python/cpython/blob/1b293b60067f6f4a95984d064ce0f6b6d34c1216/Modules/_collectionsmodule.c#L33 -ahayasiさんの deque コードリーディングのメモ:https://discord.com/channels/1084280443945353267/1200089668901937312/1235931317317799957 -(現状自分は、ソースのドキュメント部分と手持ちの教科書的な知識を照らし合わせてふーんと思った程度。また、サクッと理解できる感じではない。) -先頭要素の編集が多いならlistではなくdequeを使った方がいい。(listは可変長配列なので先頭側の削除、挿入が重い) - -条件文の作り方関連: -演算子の優先順位一覧:https://docs.python.org/3/reference/expressions.html#operator-precedence -条件式内での改行・インデントのフォーマット:https://peps.python.org/pep-0008/#indentation - From 85b83a90c709be7e9baf5a2f40c097d6f2ec6b35 Mon Sep 17 00:00:00 2001 From: sakzk Date: Fri, 10 May 2024 13:00:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E5=8F=97=E3=81=91=E3=81=A6=E4=BF=AE=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3_step4.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 3_step4.py diff --git a/3_step4.py b/3_step4.py new file mode 100644 index 0000000..13767ab --- /dev/null +++ b/3_step4.py @@ -0,0 +1,33 @@ +# time complexity: O(n) nの項の最善は最悪の 1/2倍 +# space complexity: O(n) nの項の最善は最悪の 1/n倍 + +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + longest_length = 0 + window_chars = set() + end = 0 # end finally becomes len(s). + for begin in range(len(s)): + while end < len(s) and s[end] not in window_chars: + window_chars.add(s[end]) + end += 1 + longest_length = max(end - begin, longest_length) + window_chars.remove(s[begin]) + return longest_length + +# 変更点 +""" +変数名: +window_chars : 「見たものすべて」を予想させるseen_charsから変更 +begin, end : left, rightから変更 + window_begin, window_endも検討したが、簡潔性を選んだ。 + start, stop も検討したが、substring_stop としないと落ち着かなかったので避けた。(自分の慣れの問題かもしれない) + end が、exclusiveかつ最終的にlen(s)になることをコメントに明示したが、C++では含意されているので読み手によっては冗長と感じるかも。 + +制御: +beginのループをforループに変更: + 変更前は `while begin < len(s): begin += 1` だった。自明な書き換え + 内側の end の while ループはインデックスの制限の条件に加えて追加の条件があり、rangeに書き換えると制御にフラグを導入することになり複雑になったのでwhileループのまま。 + +コメント: +最善と最悪の定数倍はビッグO記法を使わずに記述 +""" From 6dc22584977c7b76da8bc66f370fbd4d30da239f Mon Sep 17 00:00:00 2001 From: sakzk Date: Fri, 10 May 2024 13:03:40 +0900 Subject: [PATCH 9/9] Rename 3_step4.py to 03_step4.py --- 3_step4.py => 03_step4.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 3_step4.py => 03_step4.py (100%) diff --git a/3_step4.py b/03_step4.py similarity index 100% rename from 3_step4.py rename to 03_step4.py