Skip to content

Latest commit

 

History

History
348 lines (279 loc) · 13.3 KB

File metadata and controls

348 lines (279 loc) · 13.3 KB
# 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 ・開き括弧の文字列とセットの閉じ括弧が来る。 →開き括弧に対する閉じ括弧のハッシュマップを作成して、開き括弧が入力されていることをチェックした後に次の文字列があるときは、対応する閉じ括弧を入力するようにする。

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)
Input
s ="["

Use Testcase
Output
true
Expected
false

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

誤答2回目:ユースケース漏れ 追加仕様 ・開き括弧だけ単独で存在していて次の文字列がない場合、適切な文字列扱いとなるので、模範文字列が奇数の場合はfalseとする。

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 : 
            return False
        return s == ''.join(openS)
Input
s ="(("

Use Testcase
Output
true
Expected
false

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

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

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)

2nd

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

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さんレビュー ・実行時間はサーバーの空き状況で大小変わるらしくそこまで気にしなくて良いのでは? ・自前の環境で計測する方法

両方のプログラムを分析し時間・空間計算量を比較してみましょう時間計算量
模範回答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さんレビュー ・手作業でできることを機械に指示するようにコードを書く。

模範解答を見る

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

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

class Solution:
    def isValid(self, s:str) -> bool:
        stack = []
        corresponding_open_brackets={")":"(","}":"{","]":"["}

        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

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

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

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

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