Skip to content
Merged
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
227 changes: 227 additions & 0 deletions valid_parentheses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
20. Valid Parentheses

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.
Every close bracket has a corresponding open bracket of the same type.

# Step 1

とりあえずアクセプトされるコードを書く

Stackを使えば解けるのはわかる

Pythonでstackを使うには、listかdequeかの二択になりそう

まずはモジュールのかかわらないlistでやってみよう

何回かエラーが出たため、複数回提出してAC

```python

class Solution:
def isValid(self, s: str) -> bool:
still_open_brackets = []
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for ch in s:
if ch in ("(", "{", "["):
still_open_brackets.append(ch)
else:
try:
most_recent_open_bracket = still_open_brackets.pop(-1)
except:
Copy link

Choose a reason for hiding this comment

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

何も書いていない except は ^C で送られてくるシグナルさえ捕まえます。
タイミングよく ^C を送ることで、括弧の対応関係を変えられるプログラム、セキュリティー的にもまずそうではないですか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あ、exceptって普通エラーの内容を書いたほうが良いんですね。

チュートリアル「Exceptionはほぼすべての例外を捕捉するワイルドカードとして使えます。しかし良い例外処理の手法とは、処理対象の例外の型をできる限り詳細に書き、予期しない例外はそのまま伝わるようにすることです。」(https://docs.python.org/ja/3.13/tutorial/errors.html#handling-exceptions)

では、
except IndexError:
にしましょうか。

しかし、そもそもtry-except文の存在意義は
「部分的にバグっててもプログラムが止まらないようにして、それとは別にバグへの対応も出来るようにする」
ということだと思うので、条件分岐で対応できるところにtry-exceptを使うのは思想がおかしかったかもしれません。

return False
if ch != bracket_pairs[most_recent_open_bracket]:

Choose a reason for hiding this comment

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

bracket_pairs が open_to_close のような命名だと、
「ch が close bracket の時、直近みかけたopen bracketが相方か」のチェックをするのだという意図が伝わりやすくなると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうかもしれません。ただ、bracketという単語が入らないと言葉足らずな気がしています。ありがとうございます。

return False
if len(still_open_brackets) > 0:
return False
return True

```

# Step 2

ほかの人のPRを読みコード改善

Fuminitonさんのプルリクが勉強になった
会話が多いため
https://github.com/Fuminiton/LeetCode/pull/6/files

https://github.com/KTakao01/leetcode/pull/5/files

いくつか変数名変えたり、同値な変形をしています。

しかし、今回のcontinueは個人的にはあんまりわかりやすくない気がしています。

if bracket in "({["とそうでない場合は対等なイメージのため。



```python

class Solution:
def isValid(self, s: str) -> bool:
still_open_brackets = []
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for bracket in s:
if bracket in "({[":

Choose a reason for hiding this comment

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

"({["はbracket_pairs.keys()でもとれるにもかかわらず、あえてここで再度"({["を定義した理由をお聞きしたいです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

三つしか要素がないので、ベタ打ちのほうが分かりやすいかなと思いました。

still_open_brackets.append(bracket)
continue
if len(still_open_brackets) == 0:

Choose a reason for hiding this comment

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

細かいですが、listが空であるかを表すには if not を使うほうが一般的なようです。

For sequences, (strings, lists, tuples), use the fact that empty sequences are false:

Correct:

if not seq:
if seq:

Wrong:

if len(seq):
if not len(seq):

https://peps.python.org/pep-0008/#:~:text=,that%20empty%20sequences%20are%20false

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!!こんなこともPEP8に書いてあるんですね。

return False
if bracket != bracket_pairs[still_open_brackets.pop()]:
Copy link

@Fuminiton Fuminiton Mar 23, 2025

Choose a reason for hiding this comment

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

「bracketと、スタックから要素を取り出して要素を変換したものを比較し、その結果を評価している」
と一文で書くより、
「スタックから要素を取り出す」→「取り出したものを変換した値がbracketと一致するか評価する」
のように、動詞の数分、処理が分かれているほうが理解しやすいように思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

動詞の数ぶん処理を分けるというのはいい基準ですね。ありがとうございます。

return False
return len(still_open_brackets) == 0

```

次に、dequeの練習をします。

まずdequeとは何か?右からも左からもpopできる便利なデータ構造という理解ですが、ドキュメント的にはこういうことらしい。

「このモジュールは、汎用の Python 組み込みコンテナ dict, list, set, および tuple に代わる、特殊なコンテナデータ型を実装しています。」

とくにdequeは、「両端における append や pop を高速に行えるリスト風のコンテナ」

https://docs.python.org/ja/3.13/library/collections.html

ソーソコードがようやくわかった

私が
from collections import deque
と書くとき、まずpythonのcollectionsモジュールを参照して、それがCで書かれた_collectionsモジュールを参照するという建付けらしい。

結構大きな発見

https://github.com/python/cpython/blob/3.13/Lib/collections/__init__.py

https://github.com/python/cpython/blob/3.13/Modules/_collectionsmodule.c

バグもなく一発AC

```python

class Solution:
from collections import deque

def isValid(self, s: str) -> bool:
open_brackets = deque()
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for bracket in s:
if bracket in "({[":
open_brackets.append(bracket)
continue
try:
recent_open_bracket = open_brackets.pop()
if bracket != bracket_pairs[recent_open_bracket]:
return False
except:
return False
return len(open_brackets) == 0

```

ところで、ChatGPTに聞いて面白い発見をしました

まず解答

```python

from collections import deque

class Solution:
def isValid(self, s: str) -> bool:
# 括弧の対応関係を定義(閉じ括弧をキー、対応する開き括弧を値として)
mapping = {')': '(', '}': '{', ']': '['}
stack = deque()
Copy link

@olsen-blue olsen-blue Mar 29, 2025

Choose a reason for hiding this comment

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

名前と入っているものがずれているので違和感を感じます。
リストで十分だと感じますが意図などありましたらすみません。


for char in s:
# 開き括弧の場合はstackに追加
if char in mapping.values():
stack.append(char)
# 閉じ括弧の場合
elif char in mapping:
# stackが空であったり、直前の開き括弧と対応していなければ無効
if not stack or stack.pop() != mapping[char]:
return False
else:
# 問題文にない文字が現れた場合(本問題では発生しない前提)
return False

# 全ての括弧が正しく閉じられていればstackは空になっている
return not stack

```

この
if not stack or stack.pop() != mapping[char]:
の部分でpopができないとエラーが出るのではないかと思って聞いたところ、

「Pythonでは、論理演算子orは短絡評価(short-circuit evaluation)を行うため、
左側の条件がTrueの場合、右側の条件は評価されません。つまり、
if not stack or stack.pop() != mapping[char]:の部分では、
stackが空の場合、not stackがTrueとなり、stack.pop()は実行されず、
そのままTrueとしてreturn Falseが実行されます。これにより、
空のスタックに対してpop()を呼び出してエラーが発生することを防いでいます。」

とのことです。


# Step 3

三回解きます。

```python

class Solution:
def isValid(self, s: str) -> bool:
open_brackets = []
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for bracket in s:
if bracket in "({[":
open_brackets.append(bracket)
elif not open_brackets or bracket_pairs[open_brackets.pop()] != bracket:
return False
return not open_brackets

```

elif not open_brackets or bracket_pairs[open_brackets.pop()] != bracket:
の行を読むのにかなり負荷がかかるが、これは私の読解力の問題かな?
コードの構造としては、これが美しいと思うけど。

考えたのですが、やっぱり私としてはこれが「覚えられるコード」ということで、これを写経することにしました。
Copy link

Choose a reason for hiding this comment

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

その行、3つのことをしているんですよね。

  1. 中があるかを確認する。
  2. スタックから取り出す。
  3. 対応関係を確認する。
    単純な話として、たくさんのことをしていて難しいならば、分割すればいいですね。
    他には、構成要素が7以上で多いこと、2.3.で目が左右に揺れること、副作用がある複雑な式は避けること、あたりですかね。
    https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.kqo7dp4vlnzh
if bracket in "({[":
    open_brackets.append(bracket)
    continue
if not open_brackets:
    return False
if bracket_pairs[open_brackets[-1]] != bracket:
    return False
open_brackets.pop()

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね。
実は、popしなくても最後の要素を参照できることを忘れていました。

if bracket_pairs[open_brackets[-1]] != bracket:
が書けなかったから
bracket_pairs[open_brackets.pop()]
にこだわっていたということです。

これ口頭の面接だったらイエローカードですね...

Copy link

Choose a reason for hiding this comment

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

まあ、[-1] でなくても単に変数においてもいいです。pop の仕方が変わるくらいで。

if not open_brackets:
    return False
opening = open_brackets.pop()
if bracket_pairs[opening] != bracket:
    return False

いやー、まあ、これはイエローにはならないんじゃないかなと思います。(面接という極端な状況では、見落としや混乱は当然にあるよね、くらいの感覚です。)
イエローになりやすいのは、価値観がおかしい(嫌だという感情が通じない)、とかでしょうか。

たぶん、医師国家試験の禁忌肢に近い感覚です。「そんな侵襲性の高い手技を一切検査もせずに始めるのか?」とか。


```python

class Solution:
def isValid(self, s: str) -> bool:
open_brackets = []
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for bracket in s:
if bracket in "({[":
open_brackets.append(bracket)
elif not open_brackets or bracket != bracket_pairs[open_brackets.pop()]:

Choose a reason for hiding this comment

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

bracket != bracket_pairs[open_brackets.pop()]の部分が読みづらいと感じました。
「今見ているカッコ != 取り出した開きカッコに対応する閉じカッコ」という1文は、情報量が多いからだと思います。
条件文のあとでpopすることで、処理を分散させるなどしたらどうでしょうか。

Choose a reason for hiding this comment

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

同じおようなコメントが、上にありました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

return False
return not open_brackets

```

```python

class Solution:
def isValid(self, s: str) -> bool:
open_brackets = []
bracket_pairs = {"(": ")", "{": "}", "[": "]"}
for bracket in s:
if bracket in "({[":
open_brackets.append(bracket)
continue
if not open_brackets or bracket != bracket_pairs[open_brackets.pop()]:
return False
return not open_brackets

```