Skip to content
Open
342 changes: 342 additions & 0 deletions memo.md
Original file line number Diff line number Diff line change
@@ -1 +1,343 @@
# Step1

## アプローチ

* 括弧の種類ごとにスタックを用意しておく
* 開始かっこが出てきた時はスタックにappend
* 閉じ括弧が出てきた時はスタックからpop
* 最終的にスタックが空になっていたらおk
* 再帰でもループでもできそう
* 括弧の種類ごとに個数をカウントしておくのでもいいか.
* マイナスになった時点でinvalid

## Code1 (括弧の種類を数える方法, Wrong Answer)

* 問題文の条件`Open brackets must be closed in the correct order`を満たさず.
* 結局一つのstackに入れていくのが良さそう

```python
class Solution:
def isValid(self, s: str) -> bool:
parentheses1 = 0
parentheses2 = 0
parentheses3 = 0
for i in range(len(s)):
if parentheses1 < 0 or parentheses2 < 0 or parentheses3 < 0:
return False
if s[i] == "(":
parentheses1 += 1
elif s[i] == "{":
parentheses2 += 1
elif s[i] == "[":
parentheses3 += 1
elif s[i] == ")":
parentheses1 -= 1
elif s[i] == "}":
parentheses2 -= 1
elif s[i] == "]":
parentheses3 -= 1
if parentheses1 == 0 and parentheses2 == 0 and parentheses3 == 0:
return True
return False

```

## Code2 (stack)

```python
class Solution:
def isValid(self, s: str) -> bool:
unclosed_brackets = []
brackets_pairs = {
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.

命名はopen_to_closeとかにする方が良さそう
https://github.com/kunimomo/leetcode/pull/8/changes#r2771430167

"(" : ")",
"{" : "}",
"[" : "]"
}
for i in range(len(s)):
if s[i] in brackets_pairs.keys():
unclosed_brackets.append(s[i])
else:
if len(unclosed_brackets) == 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.

こちらのコメントをご参照ください。
mamo3gr/arai60#6 (comment)

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.

ありがとうございます。
if not 変数名
とした方が簡潔でいいですね

return False
last_unclosed_bracket = unclosed_brackets.pop(-1)
if brackets_pairs[last_unclosed_bracket] != s[i]:
return False
if len(unclosed_brackets) == 0:
return True
return False
```

## Code3 (再帰)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

一応、想像した再帰とは違いました。

再帰下降を見ておいてください。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.5ynll0rwu02h

Copy link
Copy Markdown
Owner Author

@kitano-kazuki kitano-kazuki Feb 13, 2026

Choose a reason for hiding this comment

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

再帰降下法はあまり馴染み無かったので実装しました
https://github.com/kitano-kazuki/leetcode/pull/6/changes#diff-cf71cdc6c0d746b654ccddf6a975ccbbea929f8e2d2fee0362ccf6e9f5a3def6

でもあまり自信はないです.
お時間あれば, こちらもレビューしていただけますと幸いです.


* `return True`や`return False`が交互に出現するのが気持ち悪かったので, `return True`が最後に登場するようにした.
* 途中計算に使う値を変数に切り出して読みやすくした

```python
BRACKET_PAIRS = {
"(" : ")",
"{" : "}",
"[" : "]"
}
class Solution:
def _isValid(self, s, idx, stack):
if idx == len(s):
if len(stack) == 0:
return True
return False
if s[idx] in BRACKET_PAIRS.keys():
stack.append(s[idx])
return self._isValid(s, idx + 1, stack)
if len(stack) == 0:
return False
last_unclosed_bracket = stack.pop(-1)
if BRACKET_PAIRS[last_unclosed_bracket] != s[idx]:
return False
return self._isValid(s, idx + 1, stack)

def isValid(self, s: str) -> bool:
return self._isValid(s, 0, [])
```

# Step2

## Code2-2 (stack)

```python
class Solution:
def isValid(self, s: str) -> bool:
unclosed_brackets = []
parentheses_pairs = {
"(" : ")",
"{" : "}",
"[" : "]"
}
open_brackets = parentheses_pairs.keys()
for i in range(len(s)):
if s[i] in open_brackets:
unclosed_brackets.append(s[i])
continue
if len(unclosed_brackets) == 0:
return False
last_unclosed_bracket = unclosed_brackets.pop(-1)
expected_close_bracket = parentheses_pairs[last_unclosed_bracket]
if s[i] != expected_close_bracket:
return False
if len(unclosed_brackets) > 0:
return False
return True
```

# Step3

## Code3-2 (stack)

```python
class Solution:
def isValid(self, s: str) -> bool:
unclosed_open_brackets = []
bracket_pairs = {
"(" : ")",
"{" : "}",
"[" : "]"
}
open_brakets = bracket_pairs.keys()
for i in range(len(s)):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

s はイテラブル ( str )ですので、for bracket in s と書くと読みやすいと思います。

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.

確かに今回はiを使ってないので, for bracket in sの方が良さそうです

if s[i] in open_brakets:
unclosed_open_brackets.append(s[i])
continue
if len(unclosed_open_brackets) == 0:
return False
last_open_bracket = unclosed_open_brackets.pop(-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.

.pop() はデフォルトがリストの最後ですので、.pop(-1)と等価ですね。
https://docs.python.org/3/tutorial/datastructures.html#more-on-lists

list.pop([i])
Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list. It raises an IndexError if the list is empty or the index is outside the list range.

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.

ありがとうございます。
半分手癖なんですけど, pop()が末尾からだよってことを明示したくて毎回pop(-1)にしてます.

expected_close_bracket = bracket_pairs[last_open_bracket]
if s[i] != expected_close_bracket:
return False
if len(unclosed_open_brackets) > 0:
return False
return True
Comment on lines +154 to +156
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ここはreturn not unclosed_open_bracketsと一行でスッキリ書けます。空のリストは falsy (implicit false) なので。
こちらも参考になると思います。

For sequences (strings, lists, tuples), use the fact that empty sequences are false, so if seq: and if not seq: are preferable to if len(seq): and if not len(seq): respectively.

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.

スタイルガイドがif seqを推奨だったんですね!知らなかったです。ありがとうございます。


```

# Step4/5

* 再帰降下法の実装をした.
* [参考](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.5ynll0rwu02h)
* 自然言語で解釈をちゃんとできたらバグなく実装できた.
* ただ, `nonlocal`な変数として処理をしている`idx`を持つのは初見だと思いつかなさそう.
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++ だとリファレンスで idx を与えることもありますね。

カプセル化して、peek (次の文字を見る) consume (次の文字を使う) などの関数を定義するのも一つです。

* 自然言語での動作の説明
* `check_chunk_valid`は, 先頭の開きかっこから始まって対応する閉じ括弧まで(以降はchunkと呼ぶ)が有効かどうかを判定する関数.
* chunkのなかは, 複数のchunkで構成されることもあるし, 単一のchunkであることもあるし, 何もないこともある
* それぞれの具体例
* ( () [] {} )
* ( {([])} )
* ( )
* これら全てに対応する, かつ`check_chunk_valid`を再利用するためには, 対応する閉じかっこが出てくるまでこの関数を呼びたい.
* `isValid`の入力は, 同じく複数チャンクからなる可能性があるので似た形式のループで`check_chunk_valid`を呼んであげる.
* 以下の形のコードは2回出現する
```python
while processing_idx < len(s):
if not check_chunk_valid():
return False
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

微妙に標準的な再帰下降構文解析とは異なっているように感じます。まあ、別にこれでもいいですが。
標準的なのだと
S -> Chunk S | ε
Chunk -> (S) | {S} | [S]
でしょう。で、parse_s と parse_chunk を両方書けばいいです。
で、最後に return parse_s() and self.pos == len(self.s) します。

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
Owner Author

Choose a reason for hiding this comment

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

LL(1)の実装もしてみました

再帰降下もLL(1)も1文字先を見て, 適用する構文を決定するという仕組みは一緒なんでしょうか.
だとすると, やっていることは再帰降下と一緒なのに実装コストがLL(1)はとても重く感じました.

それぞれの方法の比較として

  • LL(1)
    • 今回は, left-recrusiveの解消やleft factorizationはしていないが, それをすればどのような文脈自由文法も自然言語として受け取れる.
    • 構文解析と字句解析を切り分けられる ← これがいちばんのメリットっぽい??
  • 再帰降下
    • 人間が解釈して, 先読み文字と適用する構文の関係をコードに組み込む必要がある.
    • 実装はLL(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.

少し違和感があります。

文脈自由文法の真部分集合に LL(1) 文法があって、その伝統的なアルゴリズムに再帰下降とテーブル駆動があります。再帰下降はバックトラックなどをつけてもう少し広いクラスを扱うこともあります。

トークナイザーは、ここでは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.

テーブル駆動で解析できるものに関して、あらゆる文脈自由文法という表現は不適でLL(1)文法とするべきでしたね。

テーブル駆動の利点がコードの自動生成が簡単というのは考えが及んでいなかったです。ありがとうございます。




```python
class Solution:
def isValid(self, s: str) -> bool:
open_to_close = {
"(" : ")",
"{" : "}",
"[" : "]"
}
open_brackets = open_to_close.keys()
processing_idx = 0

def check_chunk_valid():
nonlocal processing_idx
if s[processing_idx] not in open_brackets:
return False
expected_end_bracket = open_to_close[s[processing_idx]]
processing_idx += 1
while processing_idx < len(s) and s[processing_idx] != expected_end_bracket:
if not check_chunk_valid():
return False
if processing_idx >= len(s):
return False
processing_idx += 1
return True

while processing_idx < len(s):
if not check_chunk_valid():
return False

return True
```

# Step6/7/8 (LL(1))

* LL(1)の実装をした.
* アルゴリズムとかは[ブログ](https://kazuki.jp.net/archives/456)にまとめた.
* `follow`で循環が起きているとバグりそうだけど, うまいこと対処する方法が思いつかなかった.
* A -> aBC
* first(C)が"#"を含む時, follow(A)を計算して, follow(B)に追加する
* B -> aAC
* follow(B)を計算して, follow(A)に追加する
* このような生成規則が与えられるとバグりそう

```python
class Parser:
def __init__(self, terminals, nonterminals, rules, start_symbol):
self.terminals = terminals
self.nonterminals = nonterminals
self.rules = rules
self.start_symbol = start_symbol
self.follow_memo = {}
self.parsing_table = {}
self._construct_parsing_table()

def first(self, string):
if len(string) == 1:
if string == "#":
return set(["#"])
if string in self.terminals:
return set([string])
result = set()
for produced_string in self.rules[string]:
result = result | self.first(produced_string)
return result

result = set()
for symbol in string:
symbol_first_result = self.first(symbol)
if "#" not in symbol_first_result:
result = result | symbol_first_result
return result
symbol_first_result.remove("#")
result = result | symbol_first_result
result.add("#")
return result

def follow(self, nonterminal):
if nonterminal not in self.nonterminals:
raise ValueError(f"{nonterminal} is not non-terminal")
if nonterminal in self.follow_memo:
return self.follow_memo[nonterminal]
result = set() if nonterminal != self.start_symbol else set(["$"])
for production_head, produced_strings in self.rules.items():
for produced_string in produced_strings:
nonterminal_idx = produced_string.find(nonterminal)
if nonterminal_idx == -1:
continue
while nonterminal_idx != -1:
remaining_string = produced_string[nonterminal_idx + 1:]
if remaining_string:
remaining_string_first_res = self.first(remaining_string)
if "#" not in remaining_string_first_res:
result = result | remaining_string_first_res
else:
production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set()
result = result | production_head_follow_result | (remaining_string_first_res - set(["#"]))
else:
production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set()
result = result | production_head_follow_result
nonterminal_idx = produced_string.find(nonterminal, nonterminal_idx + 1)
self.follow_memo[nonterminal] = result
return result

def _construct_parsing_table(self):
for nonterminal in self.nonterminals:
self.parsing_table[nonterminal] = {}
for terminal in self.terminals + ["$"]:
self.parsing_table[nonterminal][terminal] = []
for production_head, produced_strings in self.rules.items():
for produced_string in produced_strings:
produced_string_first_result = self.first(produced_string)
if "#" in produced_string_first_result:
head_follow_result = self.follow(production_head)
for terminal in head_follow_result:
self.parsing_table[production_head][terminal].append({production_head : produced_string})
produced_string_first_result.remove("#")
for terminal in produced_string_first_result:
self.parsing_table[production_head][terminal].append({production_head : produced_string})
# print(self.parsing_table)
return

def parse(self, input):
end_idx = len(input)
input = input + "$"
stack = ["$", self.start_symbol]
processing_idx = 0
while stack[-1] != "$":
symbol = stack.pop()
input_char = input[processing_idx]
if symbol == input_char:
processing_idx += 1
continue
if symbol in self.terminals:
return False
if not self.parsing_table[symbol][input_char]:
return False
production_rules = self.parsing_table[symbol][input_char]
if len(production_rules) >= 2:
return False
production_rule = production_rules[0]
produced_string = production_rule[symbol]
if produced_string != "#":
for produced_char in produced_string[::-1]:
stack.append(produced_char)
return processing_idx == end_idx

class Solution:
def isValid(self, s: str) -> bool:

nonterminals = ["S", "C"]
terminals = ["(", ")", "{", "}", "[", "]", "e"]
rules = {
"S" : ["CS","#"],
"C" : ["(S)", "{S}","[S]"]
}
start_symbol = "S"
parser = Parser(terminals, nonterminals, rules, start_symbol)
return parser.parse(s)

```
24 changes: 24 additions & 0 deletions step1-1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution:
def isValid(self, s: str) -> bool:
parentheses1 = 0
parentheses2 = 0
parentheses3 = 0
for i in range(len(s)):
if parentheses1 < 0 or parentheses2 < 0 or parentheses3 < 0:
return False
if s[i] == "(":
parentheses1 += 1
elif s[i] == "{":
parentheses2 += 1
elif s[i] == "[":
parentheses3 += 1
elif s[i] == ")":
parentheses1 -= 1
elif s[i] == "}":
parentheses2 -= 1
elif s[i] == "]":
parentheses3 -= 1
if parentheses1 == 0 and parentheses2 == 0 and parentheses3 == 0:
return True
return False

20 changes: 20 additions & 0 deletions step1-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def isValid(self, s: str) -> bool:
unclosed_brackets = []
brackets_pairs = {
"(" : ")",
"{" : "}",
"[" : "]"
}
for i in range(len(s)):
if s[i] in brackets_pairs.keys():
unclosed_brackets.append(s[i])
else:
if len(unclosed_brackets) == 0:
return False
last_unclosed_bracket = unclosed_brackets.pop(-1)
if brackets_pairs[last_unclosed_bracket] != s[i]:
return False
if len(unclosed_brackets) == 0:
return True
return False
Loading