Problem
Given a string containing only the characters (, ), {, }, [, and ], determine if the input string is valid. A string is valid if every opening bracket is closed by the same type of bracket, in the correct order.
Example:
- Input:
s = "()[]{}"โ Output:true - Input:
s = "(]"โ Output:false - Input:
s = "([)]"โ Output:false(wrong order)
Intuition
The problem asks whether every opening bracket is closed by the correct type in the correct order. The key insight is that the most recently opened bracket must be the first one closed โ that's last-in, first-out, which is exactly what a stack gives you.
Approach 1 โ Brute Force (Repeated Removal)
Repeatedly remove adjacent matching pairs ((), [], {}) until none remain. If the string is empty, it was valid.
Steps:
- Loop until no replacements happen in a full pass
- Each pass: replace
(),[],{}with an empty string - If the string is empty at the end, return
true
1def isValid(s: str) -> bool:
2 previous_length = -1
3 while len(s) != previous_length:
4 previous_length = len(s)
5 # Remove all adjacent matching pairs in one pass
6 s = s.replace("()", "").replace("[]", "").replace("{}", "")
7 return len(s) == 0- Time: O(nยฒ) โ each pass is O(n) and we may need up to n/2 passes
- Space: O(n) โ string replacement creates new strings
Approach 2 โ Stack (Optimal)
Use a stack to track unmatched opening brackets. When we see a closing bracket, check that it matches the top of the stack.
Steps:
- Build a map of closing bracket โ its expected matching opener
- For each character in the string:
- If it's an opening bracket, push it onto the stack
- If it's a closing bracket, check the stack isn't empty and the top matches
- If either check fails, return
false
- After the loop, return
trueonly if the stack is empty
1def isValid(s: str) -> bool:
2 matching_opener = {")": "(", "]": "[", "}": "{"}
3 stack = []
4
5 for char in s:
6 if char in matching_opener:
7 # Closing bracket โ check for a matching opener on top
8 if not stack or stack[-1] != matching_opener[char]:
9 return False
10 stack.pop()
11 else:
12 # Opening bracket โ save it for later matching
13 stack.append(char)
14
15 # Valid only if every opener was matched and popped
16 return len(stack) == 0- Time: O(n) โ single pass through the string
- Space: O(n) โ stack holds at most n/2 opening brackets
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force (repeated removal) | O(nยฒ) | O(n) | Never in production; useful as a first-instinct answer |
| Stack | O(n) | O(n) | Always โ this is the standard solution |
Common Mistakes
- Not checking if the stack is empty before popping: if you see
]on an empty stack, you must returnfalseimmediately. Thenot stackguard handles this. - Forgetting to check the stack is empty at the end:
"((("passes every loop iteration without returningfalse, but leaves 3 unmatched openers. The finalreturn len(stack) == 0catches this. - Pushing the closing bracket instead of checking it: only push opening brackets. Close brackets should trigger a match check, not a push.
- Wrong map direction: the map should go
closing โ openingso you can look up what a close bracket expects to find on the stack.
Related Problems
generate-parenthesesโ generate all valid combinations; requires understanding the same open/close balance ruleslongest-valid-parenthesesโ find the longest valid substring; stack-based with index trackingminimum-add-to-make-parentheses-validโ count inserts needed; same stack thinkingvalid-parenthesis-stringโ adds wildcards; greedy or stack variantdecode-stringโ uses a stack for nested structure, same pattern