20. Valid Parentheses
Problem Description
You are given a string s
that contains only bracket characters: '('
, ')'
, '{'
, '}'
, '['
and ']'
. Your task is to determine whether this string of brackets is valid.
A bracket string is considered valid when it follows these rules:
-
Same Type Matching: Every opening bracket must be closed by a bracket of the same type. For example,
'('
must be closed by')'
, not by']'
or'}'
. -
Correct Order: Brackets must be closed in the correct order. If you open bracket A then bracket B, you must close B before closing A. For example,
"([)]"
is invalid because the brackets are not closed in the correct order. -
Complete Pairing: Every closing bracket must have a corresponding opening bracket that comes before it, and every opening bracket must have a corresponding closing bracket.
The valid bracket pairs are:
'('
with')'
'['
with']'
'{'
with'}'
For example:
"()"
is valid - single pair of matching brackets"()[]{}"
is valid - multiple pairs in sequence"([{}])"
is valid - properly nested brackets"(]"
is invalid - mismatched bracket types"([)]"
is invalid - incorrect closing order"((("
is invalid - unclosed brackets
The solution uses a stack data structure to track opening brackets and verify they match with their corresponding closing brackets in the correct order. When an opening bracket is encountered, it's pushed onto the stack. When a closing bracket is found, the algorithm checks if it matches the most recent unclosed opening bracket (the top of the stack).
Intuition
Think about how you would manually check if brackets are valid when reading through them left to right. When you see an opening bracket like '('
, you need to remember it and wait for its matching closing bracket ')'
. But here's the key insight: if there are nested brackets, the most recently opened bracket must be closed first before we can close the brackets that came before it.
This "last opened, first closed" pattern is exactly how a stack works - Last In, First Out (LIFO). When we encounter an opening bracket, we need to "remember" it for later, so we push it onto a stack. When we see a closing bracket, it should match the most recent unclosed opening bracket, which is sitting at the top of our stack.
Consider the example "([)]"
. As we read through:
'('
- push to stack:['(']
'['
- push to stack:['(', '[']
')'
- this should match with'('
, but the top of stack is'['
- mismatch!
This immediately tells us the string is invalid because we're trying to close '('
before closing '['
.
The beauty of this approach is that the stack naturally maintains the order of unclosed brackets for us. Every closing bracket we encounter must match with whatever is on top of the stack. If it doesn't match, or if the stack is empty when we try to pop (meaning there's a closing bracket with no corresponding opening bracket), we know the string is invalid.
At the end, if our stack is empty, it means every opening bracket found its matching closing bracket in the correct order. If the stack still has elements, it means some opening brackets were never closed.
The solution cleverly stores valid pairs as strings like '()'
, '[]'
, '{}'
in a set, then checks if the popped opening bracket combined with the current closing bracket forms a valid pair by concatenating them and checking membership in this set.
Learn more about Stack patterns.
Solution Approach
The solution implements a stack-based approach to validate the bracket string. Here's a detailed walkthrough of the implementation:
Data Structures Used:
- A stack (
stk
) to keep track of opening brackets - A set (
d
) containing valid bracket pairs as strings:{'()', '[]', '{}'}
Algorithm Steps:
-
Initialize the stack and valid pairs set:
stk = [] d = {'()', '[]', '{}'}
-
Iterate through each character in the string: For each character
c
in strings
: -
Check if it's an opening bracket:
if c in '({[': stk.append(c)
If the character is one of
'('
,'{'
, or'['
, push it onto the stack. This stores the opening bracket for later matching. -
Handle closing brackets:
elif not stk or stk.pop() + c not in d: return False
When we encounter a closing bracket (anything else), we perform two checks:
- Empty stack check:
not stk
- If the stack is empty, there's no corresponding opening bracket for this closing bracket, so returnFalse
- Matching check:
stk.pop() + c not in d
- Pop the top element from the stack (the most recent opening bracket) and concatenate it with the current closing bracket. If this combination doesn't form a valid pair in our setd
, returnFalse
- Empty stack check:
-
Final validation:
return not stk
After processing all characters, check if the stack is empty. An empty stack means all opening brackets were properly closed. If the stack still contains elements, some opening brackets were never closed, making the string invalid.
Key Pattern - Stack for Bracket Matching: The stack pattern is perfect here because bracket matching follows a Last-In-First-Out (LIFO) principle. The most recently opened bracket must be closed first before closing any brackets that were opened before it.
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the string, as we traverse the string once - Space Complexity:
O(n)
in the worst case when all characters are opening brackets and get pushed to the stack
The elegance of this solution lies in how it leverages the stack's LIFO property to naturally handle the nesting structure of brackets and the clever use of string concatenation with set membership to validate bracket pairs.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the example string s = "{[()]}"
Initial Setup:
- Stack:
[]
(empty) - Valid pairs set:
{'()', '[]', '{}'}
Step-by-step execution:
Character 1: '{'
- This is an opening bracket (found in
'({['
) - Push to stack
- Stack:
['{']
Character 2: '['
- This is an opening bracket
- Push to stack
- Stack:
['{', '[']
Character 3: '('
- This is an opening bracket
- Push to stack
- Stack:
['{', '[', '(']
Character 4: ')'
- This is a closing bracket (not in
'({['
) - Check: Is stack empty? No, stack has elements
- Pop from stack:
'('
- Form pair:
'(' + ')' = '()'
- Check: Is
'()'
in valid pairs set? Yes! - Continue processing
- Stack:
['{', '[']
Character 5: ']'
- This is a closing bracket
- Check: Is stack empty? No
- Pop from stack:
'['
- Form pair:
'[' + ']' = '[]'
- Check: Is
'[]'
in valid pairs set? Yes! - Continue processing
- Stack:
['{']
Character 6: '}'
- This is a closing bracket
- Check: Is stack empty? No
- Pop from stack:
'{'
- Form pair:
'{' + '}' = '{}'
- Check: Is
'{}'
in valid pairs set? Yes! - Continue processing
- Stack:
[]
Final Check:
- Is stack empty? Yes (
not stk
returnsTrue
) - Result: Valid string!
Counter-example with s = "([)]"
:
Character 1: '('
→ Push to stack → Stack: ['(']
Character 2: '['
→ Push to stack → Stack: ['(', '[']
Character 3: ')'
→ Pop '['
from stack → Form pair '[)'
→ Not in valid pairs! → Return False
The algorithm correctly identifies this as invalid because we're trying to close '('
before closing '['
.
Solution Implementation
1class Solution:
2 def isValid(self, s: str) -> bool:
3 # Stack to keep track of opening brackets
4 stack = []
5
6 # Set of valid bracket pairs
7 valid_pairs = {'()', '[]', '{}'}
8
9 # Iterate through each character in the string
10 for char in s:
11 # If it's an opening bracket, push it onto the stack
12 if char in '({[':
13 stack.append(char)
14 # If it's a closing bracket
15 else:
16 # Check if stack is empty (no matching opening bracket)
17 # or if the popped opening bracket doesn't match with current closing bracket
18 if not stack or stack.pop() + char not in valid_pairs:
19 return False
20
21 # All brackets are valid if stack is empty (all opening brackets were matched)
22 return not stack
23
1class Solution {
2 /**
3 * Validates if the input string has valid matching brackets.
4 * Valid brackets must be closed in the correct order.
5 *
6 * @param s the input string containing bracket characters
7 * @return true if all brackets are properly matched and closed, false otherwise
8 */
9 public boolean isValid(String s) {
10 // Stack to keep track of opening brackets
11 Deque<Character> stack = new ArrayDeque<>();
12
13 // Iterate through each character in the string
14 for (char currentChar : s.toCharArray()) {
15 // If it's an opening bracket, push it onto the stack
16 if (currentChar == '(' || currentChar == '{' || currentChar == '[') {
17 stack.push(currentChar);
18 }
19 // If it's a closing bracket
20 else if (stack.isEmpty() || !isMatchingPair(stack.pop(), currentChar)) {
21 // Return false if stack is empty (no matching opening bracket)
22 // or if the popped opening bracket doesn't match the current closing bracket
23 return false;
24 }
25 }
26
27 // All brackets are matched if stack is empty
28 return stack.isEmpty();
29 }
30
31 /**
32 * Checks if the opening and closing brackets form a matching pair.
33 *
34 * @param openingBracket the opening bracket character
35 * @param closingBracket the closing bracket character
36 * @return true if the brackets match, false otherwise
37 */
38 private boolean isMatchingPair(char openingBracket, char closingBracket) {
39 return (openingBracket == '(' && closingBracket == ')') ||
40 (openingBracket == '{' && closingBracket == '}') ||
41 (openingBracket == '[' && closingBracket == ']');
42 }
43}
44
1class Solution {
2public:
3 /**
4 * Validates if the input string contains valid parentheses.
5 * A string is valid if:
6 * - Open brackets are closed by the same type of brackets
7 * - Open brackets are closed in the correct order
8 * - Every close bracket has a corresponding open bracket
9 *
10 * @param s Input string containing only '(', ')', '{', '}', '[', ']'
11 * @return true if the string is valid, false otherwise
12 */
13 bool isValid(string s) {
14 // Use string as a stack to store opening brackets
15 string stack;
16
17 // Iterate through each character in the input string
18 for (char current_char : s) {
19 // If current character is an opening bracket, push it onto the stack
20 if (current_char == '(' || current_char == '{' || current_char == '[') {
21 stack.push_back(current_char);
22 }
23 // If it's a closing bracket
24 else if (stack.empty() || !isMatchingPair(stack.back(), current_char)) {
25 // Stack is empty (no matching opening bracket) or
26 // brackets don't match - return false
27 return false;
28 }
29 else {
30 // Valid matching pair found, remove the opening bracket from stack
31 stack.pop_back();
32 }
33 }
34
35 // Valid only if all opening brackets have been matched (stack is empty)
36 return stack.empty();
37 }
38
39private:
40 /**
41 * Checks if the given left and right brackets form a matching pair.
42 *
43 * @param left_bracket The opening bracket character
44 * @param right_bracket The closing bracket character
45 * @return true if they form a valid pair, false otherwise
46 */
47 bool isMatchingPair(char left_bracket, char right_bracket) {
48 return (left_bracket == '(' && right_bracket == ')') ||
49 (left_bracket == '[' && right_bracket == ']') ||
50 (left_bracket == '{' && right_bracket == '}');
51 }
52};
53
1// Map of opening brackets to their corresponding closing brackets
2const bracketPairs = new Map<string, string>([
3 ['(', ')'],
4 ['[', ']'],
5 ['{', '}'],
6]);
7
8/**
9 * Validates if a string contains properly balanced and nested brackets
10 * @param s - The input string containing brackets to validate
11 * @returns true if all brackets are properly matched and nested, false otherwise
12 */
13function isValid(s: string): boolean {
14 // Stack to keep track of expected closing brackets
15 const expectedClosingBrackets: string[] = [];
16
17 // Iterate through each character in the string
18 for (const char of s) {
19 // Check if current character is an opening bracket
20 if (bracketPairs.has(char)) {
21 // Push the corresponding closing bracket onto the stack
22 expectedClosingBrackets.push(bracketPairs.get(char)!);
23 } else {
24 // Current character is a closing bracket
25 // Pop from stack and check if it matches the current closing bracket
26 if (expectedClosingBrackets.pop() !== char) {
27 return false;
28 }
29 }
30 }
31
32 // Valid if all brackets have been matched (stack is empty)
33 return expectedClosingBrackets.length === 0;
34}
35
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. This is because the algorithm iterates through each character in the string exactly once. For each character, it performs constant-time operations: checking if the character is an opening bracket, appending to the stack, popping from the stack, and checking membership in the set d
.
The space complexity is O(n)
, where n
is the length of the string s
. In the worst case, when all characters in the string are opening brackets (like "(((((("
), the stack will store all n
characters. The dictionary d
uses constant space O(1)
since it always contains exactly 3 elements regardless of input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check for Empty Stack Before Popping
One of the most common mistakes is attempting to pop from the stack without first checking if it's empty. This happens when encountering a closing bracket with no corresponding opening bracket.
Incorrect Implementation:
def isValid(self, s: str) -> bool:
stack = []
valid_pairs = {'()', '[]', '{}'}
for char in s:
if char in '({[':
stack.append(char)
else:
# WRONG: This will raise an IndexError if stack is empty
if stack.pop() + char not in valid_pairs:
return False
return not stack
What Goes Wrong:
- Input:
")("
- When processing the first
')'
, the stack is empty - Calling
stack.pop()
raises anIndexError: pop from empty list
Correct Fix: Always check if the stack is empty before attempting to pop:
if not stack or stack.pop() + char not in valid_pairs: return False
2. Using Dictionary Instead of Set for Validation
Another pitfall is using a dictionary with opening brackets as keys and closing brackets as values, which complicates the matching logic unnecessarily.
Overly Complex Approach:
def isValid(self, s: str) -> bool:
stack = []
mapping = {'(': ')', '[': ']', '{': '}'}
for char in s:
if char in mapping: # Opening bracket
stack.append(char)
else: # Closing bracket
if not stack:
return False
top = stack.pop()
if mapping[top] != char: # More complex matching logic
return False
return not stack
Why the Set Approach is Better:
- The set approach (
{'()', '[]', '{}'}
) with string concatenation is more concise - It eliminates the need for separate logic to map opening to closing brackets
- The validation becomes a simple set membership check:
stack.pop() + char in valid_pairs
3. Not Handling the Final Stack Check
Some implementations forget to verify that the stack is empty at the end, leading to false positives for strings with unclosed brackets.
Incorrect Implementation:
def isValid(self, s: str) -> bool:
stack = []
valid_pairs = {'()', '[]', '{}'}
for char in s:
if char in '({[':
stack.append(char)
else:
if not stack or stack.pop() + char not in valid_pairs:
return False
# WRONG: Forgot to check if stack still has unclosed brackets
return True # Should be: return not stack
What Goes Wrong:
- Input:
"((("
- All characters are pushed to the stack
- The function returns
True
even though there are three unclosed opening brackets - The correct behavior should return
False
becausenot stack
would beFalse
Correct Fix:
Always return not stack
instead of True
to ensure all opening brackets were properly closed.
A heap is a ...?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!