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
348 changes: 348 additions & 0 deletions 20.ValidParentheses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
```python
# 20. Valid Parentheses
# Easy
# Topics
# Companies
# Hint
# 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.
```

# 自前の回答
## 1st

誤答1回目:ユースケース漏れ
・入力文字列とそこから想定される正しい文字列を比較して、一致していれば入力値が適切、不一致ならば入力値が不適切とする。
・先に開き括弧が来て、後に閉じ括弧が来る。→閉じ括弧が先に来るときは、false
・開き括弧の文字列とセットの閉じ括弧が来る。
→開き括弧に対する閉じ括弧のハッシュマップを作成して、開き括弧が入力されていることをチェックした後に次の文字列があるときは、対応する閉じ括弧を入力するようにする。


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

# ({)} is true?
# If you don’t care about the order in which the brackets are closed, as in the example above, you can simply run a loop.
hashmap = {"(":")","{":"}","[":"]"}
openS = []
closeS = []
for i in range(len(s)) :
if s[i] == "(" or s[i] == "{" or s[i] == "[":
openS.append(s[i])
closeS.append(hashmap[s[i]])
else:
# 組み合わせとして先に閉じ括弧が来るときはfalse
if closeS == []: return false
openS.append(closeS.pop())
return s == ''.join(openS)
```


```python
Input
s ="["

Use Testcase
Output
true
Expected
false
```
Comment on lines +46 to +56

This comment was marked as resolved.


考慮漏れ
・開き括弧だけが入力文字列として存在する場合でもTrueになる。


誤答2回目:ユースケース漏れ

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.

ありがとうございます。

追加仕様
・開き括弧だけ単独で存在していて次の文字列がない場合、適切な文字列扱いとなるので、模範文字列が奇数の場合はfalseとする。

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

# ({)} is true?
# If you don’t care about the order in which the brackets are closed, as in the example above, you can simply run a loop.
hashmap = {"(":")","{":"}","[":"]"}
openS = []

Choose a reason for hiding this comment

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

「S」が何を指すか伝わりにくかったです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。1回目の誤答のため放置しておりました。2回目にて変数を変えております。

closeS = []
for i in range(len(s)) :

Choose a reason for hiding this comment

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

sのインデックスは特に使用しないので、
「for bracket in s:」のようにした方が読み手が楽に理解できると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに!

if s[i] == "(" or s[i] == "{" or s[i] == "[":
openS.append(s[i])
closeS.append(hashmap[s[i]])
else:
# 組み合わせとして先に閉じ括弧が来るときはfalse
if closeS == []: return False
openS.append(closeS.pop())
Comment on lines +63 to +82

This comment was marked as resolved.

if len(openS) % 2 == 1 :
return False
return s == ''.join(openS)
```
Comment on lines +67 to +86

This comment was marked as resolved.


``` python
Input
s ="(("

Use Testcase
Comment on lines +84 to +92

This comment was marked as resolved.

Output
true
Expected
false
```

考慮漏れ:偶数文字数であっても開き括弧だけが連続して単独に存在するケースがある。


正答1回目:
・奇数文字列かつ開き括弧に対応する閉じ括弧を文字列に使っていない場合falseとすることで、
開き括弧だけの入力文字列を文字数にかかわらずfalseにできる。


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

# ({)} is true?
# If you don’t care about the order in which the brackets are closed, as in the example above, you can simply run a loop.
hashmap = {"(":")","{":"}","[":"]"}
openS = []
closeS = []
for i in range(len(s)) :
if s[i] == "(" or s[i] == "{" or s[i] == "[":
openS.append(s[i])
closeS.append(hashmap[s[i]])
else:
# 組み合わせとして先に閉じ括弧が来るときはfalse
if closeS == []: return False
openS.append(closeS.pop())
if len(openS) % 2 == 1 or len(closeS) != 0 :
return False
return s == ''.join(openS)
```
Copy link

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.7n6wwffw10hb

Copy link
Owner Author

@KTakao01 KTakao01 Mar 1, 2025

Choose a reason for hiding this comment

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

ありがとうございます!自分でも複雑怪奇な解き方をしがちで、ずっと悩んでいました。
「手でやるのと同じように機械に指示させる」は一皮剥けるのにとても参考になる指針だと思いました。
以後気をつけてみます。


## 2nd
コードを見やすくする
・変数名が指示する内容を表すように修正


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

# ({)} is true?
# If you don’t care about the order in which the brackets are closed, as in the example above, you can simply run a loop.
brackets = {"(":")","{":"}","[":"]"}
ideal_strings = []
corresponding_close_brackets = []
for bracket in range(len(s)) :
if s[bracket] == "(" or s[bracket] == "{" or s[bracket] == "[":
ideal_strings.append(s[bracket]) # 開き括弧を記録する
corresponding_close_brackets.append(brackets[s[bracket]]) # 閉じ括弧
else:
# 組み合わせとして先に閉じ括弧が来るときはfalse
if corresponding_close_brackets == []: return False
ideal_strings.append(corresponding_close_brackets.pop()) # 閉じ括弧に来るべき括弧の種類を考える
if len(ideal_strings) % 2 == 1 or len(corresponding_close_brackets) != 0 :
return False
return s == ''.join(ideal_strings)
```

fuga-98さんレビュー
・corresponding_close_brackets→open_to_closeなどとキーとバリューのわかる名前のほうがよさそう。


# 模範回答
・括弧の対応において全体の式が有効ならば、部分的に取り出した括弧の式も有効
→開き括弧に同タイプの閉じ括弧が対応していない場合はFalseというのは、自前の回答と同じ考え
・スタックをうまく使う。自前の回答はリストを2つ使っている。その分計算量が大きくなる。

## 1st
~~・模範回答が0msで自前の回答が1msで模範回答の方が速かった。(この計測は偶然の可能性あり・・・)~~
原因は主に2つ。
・時間計算量:処理の最後に理想的な文字列と実際の文字列を比較している
・空間計算量:リストを2つ使用および1つのリストのメモリ占有がそもそも大きいと
詳細は以下。

Fumintonさんレビュー
・実行時間はサーバーの空き状況で大小変わるらしくそこまで気にしなくて良いのでは?
・[自前の環境で計測する方法](
https://stackoverflow.com/questions/513882/python-list-vs-dict-for-look-up-table)

```python
両方のプログラムを分析し、時間・空間計算量を比較してみましょう。
時間計算量
模範回答(0ms):

文字列sを1回だけ走査(O(n))
スタック操作(push/pop)は定数時間(O(1))
マッピング検索も定数時間(O(1))
全体:O(n)

自前の回答(1ms):

文字列sを1回だけ走査(O(n))
ideal_stringsとcorresponding_close_bracketsへの追加/削除操作(O(1))
最後にs == ''.join(openS)で文字列結合と比較(O(n))
全体:O(n)

時間計算量的には両方ともO(n)ですが、自前の回答の最後に''.join(openS)という操作があり、これは追加のO(n)時間を要します。
空間計算量
模範回答:

スタック:最悪の場合O(n)(全ての文字が開き括弧の場合)
マッピング辞書:定数サイズO(1)
全体:O(n)

自前の回答:

ideal_strings:最悪の場合O(n)
corresponding_close_brackets:最悪の場合O(n)
全体:O(n)

ただし、自前の回答は2つのリストを使用するため、実質的に2倍のメモリを使用します。
速度の差の理由

余分な操作: 自前の回答は''.join(openS)で文字列結合を行い、元の文字列と比較しています。これは不要なO(n)の操作です。
2つのリストの使用: 自前の回答はideal_stringsとcorresponding_close_bracketsという2つのリストを管理しています。これに対し、模範回答はスタック1つだけです。
アルゴリズムの効率: 模範回答は必要最小限の操作だけを行い、閉じ括弧が来たときにすぐに検証します。自前の回答は閉じ括弧が来たときに対応する開き括弧をideal_stringsに追加し、最後に全体を検証します。
コードの問題点: 自前の回答にはopenSという変数が参照されていますが、定義されていません。おそらくideal_stringsの間違いです。また、({)}のようなケースをTrue扱いするコメントがありますが、実際のコードの結果はFalseになります。

まとめ
模範回答の方が速いのは主に以下の理由によります:

より少ないメモリ使用量(スタック1つのみ)
余分な文字列操作の欠如(join不要)
より効率的なアルゴリズム設計(閉じ括弧が来たときに即検証)

自前の回答は同じ計算量を持ちますが、定数倍の違いが実行時間の差として現れています。また、コード内のエラーや余分な操作も実行時間に影響しています。
```

odaさんレビュー
・手作業でできることを機械に指示するようにコードを書く。


模範解答を見る
``` python
class Solution(object):
def isValid(self, s: str) -> bool:

# The stack to keep track of opening brackets.
stack = []

# Hash map for keeping track of mappings. This keeps the code very clean.
Comment on lines +230 to +238
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

模範回答の再現部分

ここでは、模範回答に沿った実装例が再現されていますが、def isValid(self, s:string) -> bool: として、型ヒント部分で string となっているのは誤りです。Pythonでは型ヒントとして str を用いるべきです。

以下のdiffを適用してください:

-    def isValid(self, s:string) -> bool:
+    def isValid(self, s: str) -> bool:

Committable suggestion skipped: line range outside the PR's diff.

# Also makes adding more types of parenthesis easier
mapping = {")": "(", "}": "{", "]": "["}

# For every bracket in the expression.
for char in s:

# If the character is an closing bracket
if char in mapping:

# Pop the topmost element from the stack, if it is non empty
# Otherwise assign a dummy value of '#' to the top_element variable
top_element = stack.pop() if stack else "#"

# The mapping for the opening bracket in our hash and the top
# element of the stack don't match, return False
if mapping[char] != top_element:
return False
else:
# We have an opening bracket, simply push it onto the stack.
stack.append(char)

# In the end, if the stack is empty, then we have a valid expression.
# The stack won't be empty for cases like ((()
return not stack
```

模範回答を再現する
・頭の中でコードを表面的に理解(解説とコードの対応を暗記)、記憶したら、何も見ずに、その記憶のコードを咀嚼して深く理解する(解説の内容を腹落ち)
・実際に書いてみてチェックする

```python
class Solution:
def isValid(self, s:str) -> bool:
stack = []

Choose a reason for hiding this comment

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

stackの命名は改善の余地があると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

open_brackets_stackとかでしょうか

corresponding_open_brackets={")":"(","}":"{","]":"["}
Copy link

Choose a reason for hiding this comment

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

open_to_closeなどのキーとバリューがわかる名前のほうが好みです

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。確かにです。


for char in s:
if char in corresponding_open_brackets:
expected_char = stack.pop() if stack else "#"
if expected_char != corresponding_open_brackets[char]:
return False
else:
stack.append(char)
return not stack
```

Fuminitonさんレビュー
・stackの命名は改善の余地あり

# 2nd
~~・自前の回答で変数名の修正を行なったのでここでは省略する。~~
・レビューで別観点の修正があれば行う。

``` python
class Solution:
def isValid(self, s:str) -> bool:
open_brackets_stack = []
clese_to_open={")":"(","}":"{","]":"["}

for char in s:
if char in clese_to_open:
expected_char = open_brackets_stack.pop() if open_brackets_stack else "#"
if expected_char != clese_to_open[char]:
return False
else:
open_brackets_stack.append(char)
return not open_brackets_stack
```

手作業
・閉じ括弧か開き括弧か見る。開き括弧なら、メモする、閉じ括弧ならメモがあるならそのメモの最新の開きカッコを取り出して、同じ種類の閉じ括弧と、今の閉じ括弧をみくらべて、一致していることを確認。

# 3rd
~~・自前で正答まで辿り着いたことと、1回目で3分で正解を再現できたため省略する。~~
・時間を測って再現する。

1回目 00:09:52

``` python
class Solution:
def isValid(self,s: str) -> bool:
open_brackets_stack = []
close_to_open = {")":"(","}":"{","]":"["}

for char in s:
if char in close_to_open:
expected_close = open_brackets_stack.pop() if open_brackets_stack else "#"
if expected_close != close_to_open[char]:
return False
else:
open_brackets_stack.append(char)
return not open_brackets_stack
```


2回目 00:08:00
``` python
class Solution:
def isValid(self, s: str) -> bool:
close_to_open = {")":"(", "}":"{", "]":"["}
open_brackets_stack = []
for char in s:
if char in close_to_open:
expected_bracket = open_brackets_stack.pop() if open_brackets_stack else "#"
if expected_bracket != close_to_open[char]:
return False
else:
open_brackets_stack.append(char)
return not open_brackets_stack
```