[Easy] 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.
Examples
Example 1:
Input: s = "()"
Output: true
Example 2:
Input: s = "()[]{}"
Output: true
Example 3:
Input: s = "(]"
Output: false
Example 4:
Input: s = "([)]"
Output: false
Example 5:
Input: s = "{[]}"
Output: true
Constraints
1 <= s.length <= 10^4sconsists of parentheses only'()[]{}'.
Thinking Process
- Stack for LIFO: Opening brackets must close in reverse order
- Stack matches nested or LIFO structure (parentheses, monotonic scans).
- Push on open / larger; pop when the current element resolves pending work.
- Monotonic stack finds next greater/smaller in O(n).
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Monotonic stack | O(n) | O(n) | Next greater/smaller element |
| Parentheses matching (this problem) | O(n) | O(n) | Push open, pop on close |
| Expression evaluation | O(n) | O(n) | Operand + operator stacks |
| Stack simulation | O(n) | O(n) | Process in LIFO order |
Solution
Time Complexity: O(n)
Space Complexity: O(n)
Use a stack to track opening brackets. When encountering a closing bracket, check if it matches the most recent opening bracket. If stack is empty at the end, all brackets are matched.
class Solution:
def isValid(self, s: str) -> bool:
st: list[str] = []
close_to_open = {")": "(", "]": "[", "}": "{"}
for c in s:
if c in "([{":
st.append(c)
else:
if not st or st[-1] != close_to_open[c]:
return False
st.pop()
return len(st) == 0
Solution Explanation
Approach: Parentheses matching (this problem)
Key idea: 1. Stack for LIFO: Opening brackets must close in reverse order
How the code works:
- Stack for LIFO: Opening brackets must close in reverse order
- Stack matches nested or LIFO structure (parentheses, monotonic scans).
- Push on open / larger; pop when the current element resolves pending work.
- Monotonic stack finds next greater/smaller in O(n).
Walkthrough — input s = "()", expected output true:
- Initialize variables from the problem setup.
- Apply the main loop / recursion until the condition is met.
- Confirm the result matches the expected output.
| Aspect | Complexity | |——–|————| | Time | O(n) - Single pass through string, each operation is O(1) | | Space | O(n) - Stack can hold at most n/2 opening brackets in worst case |
Algorithm Breakdown
1. Initialize Stack and Map
st: list[str] = []
close_to_open = {")": "(", "]": "[", "}": "{"}
- Stack: Stores opening brackets
- Map: Maps closing brackets to their corresponding opening brackets
2. Process Opening Brackets
if c in "([{":
st.append(c)
- Push opening brackets onto stack
- They will be matched later when closing brackets appear
3. Process Closing Brackets
else:
if not st or st[-1] != close_to_open[c]:
return False
st.pop()
- Check if stack is empty: No opening bracket to match
- Check if top matches: Most recent opening bracket must match current closing bracket
- Pop if matched: Remove the matched opening bracket
4. Final Validation
return len(st) == 0
- If stack is empty, all brackets were matched
- If stack has remaining elements, some opening brackets were never closed
Complexity
| Aspect | Complexity | |——–|————| | Time | O(n) - Single pass through string, each operation is O(1) | | Space | O(n) - Stack can hold at most n/2 opening brackets in worst case |
Common Mistakes
- Empty string:
""→true(valid by definition) - Single bracket:
"("or")"→false - Only opening:
"((("→false - Only closing:
")))"→false - Nested valid:
"([{}])"→true - Interleaved invalid:
"([)]"→false -
Mixed valid:
"()[]{}"→true - Not checking stack empty: Forgetting to check
st.empty()beforest.top() - Wrong map direction: Mapping opening → closing instead of closing → opening
- Not returning false immediately: Continuing after finding a mismatch
- Forgetting final check: Not checking if stack is empty at the end
- Using wrong comparison: Comparing
st.top() == cinstead ofst.top() == map[c]
Detailed Example Walkthrough
Example 1: s = "([{}])"
Step 0: Initialize
st = []
map = {')': '(', ']': '[', '}': '{'}
Step 1: c = '('
Opening bracket → push
st = ['(']
Step 2: c = '['
Opening bracket → push
st = ['(', '[']
Step 3: c = '{'
Opening bracket → push
st = ['(', '[', '{']
Step 4: c = '}'
Closing bracket → check
st.empty()? No
st.top() = '{'
map['}'] = '{'
'{' == '{'? Yes → pop
st = ['(', '[']
Step 5: c = ']'
Closing bracket → check
st.empty()? No
st.top() = '['
map[']'] = '['
'[' == '['? Yes → pop
st = ['(']
Step 6: c = ')'
Closing bracket → check
st.empty()? No
st.top() = '('
map[')'] = '('
'(' == '('? Yes → pop
st = []
Final: st.empty()? Yes → return true ✓
Example 2: s = "([)]"
Step 0: Initialize
st = []
Step 1: c = '('
Opening bracket → push
st = ['(']
Step 2: c = '['
Opening bracket → push
st = ['(', '[']
Step 3: c = ')'
Closing bracket → check
st.empty()? No
st.top() = '['
map[')'] = '('
'[' == '('? No → return false ✗
Why Stack Works
LIFO Property
Parentheses matching requires Last In, First Out:
- Most recent opening bracket must match the next closing bracket
- Stack naturally provides LIFO behavior
Example: "([{}])"
Opening order: ( [ {
Closing order: } ] )
↑
Must close in reverse order
Counter Example: "([)]"
Opening order: ( [
Closing order: ) ]
↑
Wrong order! Can't close ')' before ']'
Optimization Tips
Early Exit
Already optimized - we return false immediately on mismatch.
Memory Optimization
For very large strings, consider using a string as stack (if your use case allows) to potentially reduce allocations.
Branch Prediction
The hash map lookup is very fast, but explicit checks might be slightly faster due to branch prediction:
class Solution:
def isValid(self, s: str) -> bool:
st: list[str] = []
for c in s:
if c in "([{":
st.append(c)
else:
if not st:
return False
want = {"(": ")", "[": "]", "{": "}"}[st[-1]]
if c != want:
return False
st.pop()
return len(st) == 0
Related Problems
- 22. Generate Parentheses - Generate all valid parentheses
- 32. Longest Valid Parentheses - Find longest valid substring
- 301. Remove Invalid Parentheses - Remove minimum to make valid
- 1249. Minimum Remove to Make Valid Parentheses - Remove invalid characters
- 1541. Minimum Insertions to Balance a Parentheses String - Add minimum to balance
Pattern Recognition
This problem demonstrates the Stack for Matching pattern:
- Use stack when you need to match elements in reverse order
- Perfect for nested structures (parentheses, brackets, tags)
- LIFO property naturally handles nested matching
Key Insight:
- Opening brackets → push
- Closing brackets → pop and verify match
- Stack empty at end → all matched
Applications:
- HTML/XML tag validation
- Expression evaluation
- Function call tracking
- Nested structure parsing
Code Quality Notes
- Readability: Hash map makes code clean and extensible
- Efficiency: Optimal O(n) time and space
- Correctness: Handles all edge cases properly
- Maintainability: Easy to add new bracket types
Extending to More Bracket Types
The solution easily extends to other bracket types:
class Solution:
def isValid(self, s: str) -> bool:
stack = ""
for c in s:
if c in "([{":
stack += c
else:
if not stack:
return False
last = stack[-1]
stack = stack[:-1]
if (c == ")" and last != "(") or (c == "]" and last != "[") or (c == "}" and last != "{"):
return False
return len(stack) == 0
This is a fundamental stack problem that demonstrates the LIFO property perfectly. It’s an excellent introduction to stack-based algorithms and pattern matching.
Key Takeaways
- Stack for LIFO: Opening brackets must close in reverse order
- Map for Matching: Use hash map to map closing to opening brackets
- Empty Stack Check: All brackets matched if stack is empty at end
- Early Return: Return false immediately on mismatch or empty stack with closing bracket
References
- LC 20: Valid Parentheses on LeetCode
- LeetCode Discuss — LC 20: Valid Parentheses
- LeetCode Editorial (may require premium)