Facebook Pixel

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:

  1. 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 '}'.

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

  3. 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).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Initialize the stack and valid pairs set:

    stk = []
    d = {'()', '[]', '{}'}
  2. Iterate through each character in the string: For each character c in string s:

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

  4. 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 return False
    • 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 set d, return False
  5. 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) where n 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 Evaluator

Example 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 returns True)
  • 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 an IndexError: 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 because not stack would be False

Correct Fix: Always return not stack instead of True to ensure all opening brackets were properly closed.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More