A detailed explanation of checking whether a bracket string is valid using a stack.
Problem Restatement
We are given a string s containing only these six bracket characters:
( ) { } [ ]We need to determine whether the bracket string is valid.
A string is valid when:
- Every opening bracket is closed by the same type of bracket.
- Opening brackets are closed in the correct order.
- Every closing bracket has a matching opening bracket of the same type.
For example:
"()[]{}"is valid.
But:
"([)]"is invalid, because [ is opened after (, so ] must close before ). The source statement defines exactly these bracket types and validity rules.
Input and Output
| Item | Meaning |
|---|---|
| Input | A string s containing only bracket characters |
| Output | true if the string is valid, otherwise false |
| Bracket types | (), {}, [] |
| Valid order | The most recently opened bracket must close first |
Example function shape:
def isValid(s: str) -> bool:
...Examples
Example 1:
s = "()"The opening bracket ( is closed by ).
Output:
trueExample 2:
s = "()[]{}"Each pair is complete and correctly ordered.
Output:
trueExample 3:
s = "(]"The opening bracket ( cannot be closed by ].
Output:
falseExample 4:
s = "([)]"The brackets are closed in the wrong order.
Output:
falseExample 5:
s = "{[]}"The inner [] closes first, then the outer {} closes.
Output:
trueFirst Thought: Count Brackets
One tempting idea is to count each bracket type.
For example, count how many ( and ) appear.
But counts alone are not enough.
Consider:
"([)]"The counts are balanced:
| Bracket | Count |
|---|---|
( | 1 |
) | 1 |
[ | 1 |
] | 1 |
But the string is still invalid because the closing order is wrong.
We need to track nesting order, not only counts.
Key Insight
Brackets follow a last-opened, first-closed rule.
For example:
{ [ ( ) ] }The most recent opening bracket is (, so it must be closed by ) before [ or { can close.
This is exactly the behavior of a stack.
A stack lets us:
- push opening brackets
- pop the most recent opening bracket when we see a closing bracket
- check whether the popped bracket matches the closing bracket
Visual Walkthrough
Use:
s = "{[]}"Start with an empty stack.
Read {:
stack = ["{"]Read [:
stack = ["{", "["]Read ].
The top of the stack is [.
It matches ], so pop it:
stack = ["{"]Read }.
The top of the stack is {.
It matches }, so pop it:
stack = []The string is finished and the stack is empty.
So the string is valid.
Now compare:
s = "([)]"Read (:
stack = ["("]Read [:
stack = ["(", "["]Read ).
The top of the stack is [, but ) needs (.
Mismatch.
Return false.
Algorithm
- Create an empty stack.
- Create a map from closing bracket to matching opening bracket.
- Scan each character in
s. - If the character is an opening bracket, push it.
- If the character is a closing bracket:
- if the stack is empty, return
false - pop the top opening bracket
- if it does not match, return
false
- if the stack is empty, return
- After scanning all characters, return whether the stack is empty.
Correctness
The stack stores opening brackets that have not been closed yet.
Its top is always the most recently opened unmatched bracket.
When the algorithm sees a closing bracket, the only bracket that may legally match it is the top of the stack. If the stack is empty, there is no opening bracket for this closing bracket. If the top has a different type, the closing order or bracket type is invalid.
If the top matches, popping it correctly marks that pair as closed.
After all characters are processed, any remaining opening bracket in the stack has no matching closing bracket. So the string is valid exactly when the stack is empty.
Therefore the algorithm returns true exactly for valid bracket strings.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each character is processed once |
| Space | O(n) | The stack may store all opening brackets |
Implementation
class Solution:
def isValid(self, s: str) -> bool:
pairs = {
")": "(",
"}": "{",
"]": "[",
}
stack = []
for char in s:
if char in pairs:
if not stack:
return False
top = stack.pop()
if top != pairs[char]:
return False
else:
stack.append(char)
return len(stack) == 0Code Explanation
The map stores the required opening bracket for each closing bracket:
pairs = {
")": "(",
"}": "{",
"]": "[",
}The stack stores unmatched opening brackets:
stack = []When char is a closing bracket:
if char in pairs:we need a previous opening bracket.
If the stack is empty:
if not stack:
return Falsethen this closing bracket has nothing to close.
Otherwise, pop the most recent opening bracket:
top = stack.pop()Check whether it matches:
if top != pairs[char]:
return FalseIf char is not a closing bracket, it must be an opening bracket, so push it:
stack.append(char)At the end:
return len(stack) == 0The stack must be empty for all brackets to be closed.
Testing
def run_tests():
s = Solution()
assert s.isValid("()") is True
assert s.isValid("()[]{}") is True
assert s.isValid("(]") is False
assert s.isValid("([)]") is False
assert s.isValid("{[]}") is True
assert s.isValid("]") is False
assert s.isValid("((") is False
assert s.isValid("([]{})") is True
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"()" | Single valid pair |
"()[]{}" | Multiple adjacent valid pairs |
"(]" | Wrong bracket type |
"([)]" | Wrong closing order |
"{[]}" | Nested valid brackets |
"]" | Closing bracket without opener |
"((" | Unclosed opening brackets |
"([]{})" | Mixed nesting and adjacency |