Skip to content

101. Valid Parentheses

πŸ”— LeetCode Problem: 20. Valid Parentheses
πŸ“Š Difficulty: Easy
🏷️ Topics: Stack, String

Problem Statement

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if: 1. Open brackets must be closed by the same type of brackets. 2. Open brackets must be closed in the correct order. 3. Every close bracket has a corresponding open bracket of the same type.

Example 1:

Input: s = "()"
Output: true

Example 2:

Input: s = "()[]{}"
Output: true

Example 3:

Input: s = "(]"
Output: false

Example 4:

Input: s = "([])"
Output: true

Constraints: - 1 <= s.length <= 10^4 - s consists of parentheses only '()[]{}'.


🌟 ELI5: The Simple Idea!

Think of opening and closing doors:

Valid sequence:
  Open door A β†’ Open door B β†’ Close door B β†’ Close door A
  (           [             ]               )  βœ“

Invalid sequence:
  Open door A β†’ Open door B β†’ Close door A β†’ Close door B
  (           [             )               ]  βœ—

The MOST RECENT opened door must be closed FIRST!
This is LIFO = Stack!

Real-world analogy:

Like nesting boxes:
  Valid:
    Box1[ Box2{ Box3( ) } ]  βœ“
    Each inner box closed before outer

  Invalid:
    Box1[ Box2{ Box3( ] ) }  βœ—
    Closed Box1 before Box3!


🎨 Visual Understanding

Valid Example

Input: "({[]})"

Step-by-step with stack:

Char: '('
  Opening bracket β†’ Push to stack
  Stack: ['(']

Char: '{'
  Opening bracket β†’ Push to stack
  Stack: ['(', '{']

Char: '['
  Opening bracket β†’ Push to stack
  Stack: ['(', '{', '[']

Char: ']'
  Closing bracket β†’ Check top
  Top is '[' β†’ Match! Pop
  Stack: ['(', '{']

Char: '}'
  Closing bracket β†’ Check top
  Top is '{' β†’ Match! Pop
  Stack: ['(']

Char: ')'
  Closing bracket β†’ Check top
  Top is '(' β†’ Match! Pop
  Stack: []

End: Stack empty β†’ Valid! βœ“

Invalid Example

Input: "([)]"

Step-by-step:

Char: '('
  Stack: ['(']

Char: '['
  Stack: ['(', '[']

Char: ')'
  Top is '[' β†’ Expected ']' but got ')'
  Mismatch! β†’ Invalid! βœ—

🎯 Approach: Stack with HashMap ⭐

The Optimal Solution

Algorithm:

1. Create stack and mapping of closing β†’ opening
2. For each character:
   - If opening bracket: push to stack
   - If closing bracket:
     - Check if stack is empty (no matching opening)
     - Pop and verify it matches
3. Return stack.isEmpty() (all matched)

Implementation

/**
 * Stack-based matching with HashMap
 * Time: O(n), Space: O(n)
 */
public boolean isValid(String s) {
    // Stack to track opening brackets
    Stack<Character> stack = new Stack<>();

    // Map closing brackets to their opening counterparts
    Map<Character, Character> pairs = new HashMap<>();
    pairs.put(')', '(');
    pairs.put('}', '{');
    pairs.put(']', '[');

    for (char c : s.toCharArray()) {
        if (pairs.containsKey(c)) {
            // Closing bracket
            if (stack.isEmpty()) {
                return false;  // No matching opening
            }

            if (stack.pop() != pairs.get(c)) {
                return false;  // Wrong opening bracket
            }
        } else {
            // Opening bracket
            stack.push(c);
        }
    }

    // All brackets must be matched
    return stack.isEmpty();
}

⏰ Time: O(n) - Single pass through string
πŸ’Ύ Space: O(n) - Stack stores up to n/2 brackets in worst case

Alternative (More Concise)

public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();

    for (char c : s.toCharArray()) {
        if (c == '(') {
            stack.push(')');
        } else if (c == '{') {
            stack.push('}');
        } else if (c == '[') {
            stack.push(']');
        } else if (stack.isEmpty() || stack.pop() != c) {
            return false;
        }
    }

    return stack.isEmpty();
}

Clever trick: Push the EXPECTED closing bracket!

πŸ” Super Detailed Dry Run

Example: s = "{[()]}"

Goal: Validate all brackets match correctly

═══════════════════════════════════════════════════════════════
Setup
═══════════════════════════════════════════════════════════════

stack = []
pairs = {')': '(', '}': '{', ']': '['}

═══════════════════════════════════════════════════════════════
Iteration 1: c = '{'
═══════════════════════════════════════════════════════════════

Is '{' in pairs (closing brackets)? No
Action: Opening bracket β†’ Push to stack

Stack: ['{']

═══════════════════════════════════════════════════════════════
Iteration 2: c = '['
═══════════════════════════════════════════════════════════════

Is '[' in pairs? No
Action: Opening bracket β†’ Push to stack

Stack: ['{', '[']

═══════════════════════════════════════════════════════════════
Iteration 3: c = '('
═══════════════════════════════════════════════════════════════

Is '(' in pairs? No
Action: Opening bracket β†’ Push to stack

Stack: ['{', '[', '(']

═══════════════════════════════════════════════════════════════
Iteration 4: c = ')'
═══════════════════════════════════════════════════════════════

Is ')' in pairs? Yes (closing bracket)
Expected opening: pairs.get(')') = '('

Check: Is stack empty? No βœ“
Pop from stack: '('
Match check: '(' == '('? Yes βœ“

Stack: ['{', '[']

═══════════════════════════════════════════════════════════════
Iteration 5: c = ']'
═══════════════════════════════════════════════════════════════

Is ']' in pairs? Yes
Expected opening: pairs.get(']') = '['

Check: Is stack empty? No βœ“
Pop from stack: '['
Match check: '[' == '['? Yes βœ“

Stack: ['{']

═══════════════════════════════════════════════════════════════
Iteration 6: c = '}'
═══════════════════════════════════════════════════════════════

Is '}' in pairs? Yes
Expected opening: pairs.get('}') = '{'

Check: Is stack empty? No βœ“
Pop from stack: '{'
Match check: '{' == '{'? Yes βœ“

Stack: []

═══════════════════════════════════════════════════════════════
Final Check
═══════════════════════════════════════════════════════════════

All characters processed
Is stack empty? Yes βœ“

Return: true

Result: Valid parentheses! βœ“

Example 2: s = "([)]" (Invalid)

═══════════════════════════════════════════════════════════════
Process
═══════════════════════════════════════════════════════════════

c = '(': stack = ['(']
c = '[': stack = ['(', '[']
c = ')': Pop '[', Expected '(' but got '['
         '[' != '(' βœ—

Return: false

Result: Invalid! βœ—

Example 3: s = "((" (Invalid)

═══════════════════════════════════════════════════════════════
Process
═══════════════════════════════════════════════════════════════

c = '(': stack = ['(']
c = '(': stack = ['(', '(']

End of string
Is stack empty? No βœ—

Return: false

Result: Invalid (unmatched opening brackets)! βœ—

Example 4: s = "))" (Invalid)

═══════════════════════════════════════════════════════════════
Process
═══════════════════════════════════════════════════════════════

c = ')': Is stack empty? Yes βœ—

Return: false immediately

Result: Invalid (no matching opening)! βœ—

🎯 Why This Solution Works

The Stack Insight

Why Stack?
  - Most recent opening must match first closing
  - This is LIFO (Last In, First Out)
  - Stack naturally provides LIFO

Example:
  Input: "( [ ] )"

  After "(": stack = ['(']
  After "[": stack = ['(', '[']
           Must close '[' before '('

  When "]": Pop '[' (most recent)
  When ")": Pop '(' (next most recent)

Three Conditions for Valid

1. Every closing has a matching opening
   β†’ Stack must not be empty when closing comes

2. Correct type of bracket
   β†’ Popped opening must match closing

3. All brackets paired
   β†’ Stack must be empty at end

Why HashMap?

Map<Character, Character> pairs = {
    ')': '(',
    '}': '{',
    ']': '['
}

Advantages:
  βœ“ Easy to check if closing bracket
  βœ“ Get expected opening in O(1)
  βœ“ Extensible (easy to add new bracket types)
  βœ“ Clean code

🎯 Alternative Approach: Push Expected Closing

The Clever Trick

public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();

    for (char c : s.toCharArray()) {
        // Push the EXPECTED closing bracket
        if (c == '(') stack.push(')');
        else if (c == '{') stack.push('}');
        else if (c == '[') stack.push(']');

        // Check if closing matches expected
        else if (stack.isEmpty() || stack.pop() != c) {
            return false;
        }
    }

    return stack.isEmpty();
}

Why this works:

Opening '(' β†’ Push what we EXPECT to see: ')'
Closing ')' β†’ Check if it matches top

Example: "({[]})"

c = '(': stack = [')']
c = '{': stack = [')', '}']
c = '[': stack = [')', '}', ']']
c = ']': Pop ']', matches c βœ“, stack = [')', '}']
c = '}': Pop '}', matches c βœ“, stack = [')']
c = ')': Pop ')', matches c βœ“, stack = []

Clean! βœ“


⚠️ Common Mistakes

Mistake 1: Not checking empty stack before pop

// ❌ WRONG - EmptyStackException for ")"
if (stack.pop() != pairs.get(c)) {
    return false;
}

// βœ“ CORRECT - Check first
if (stack.isEmpty()) {
    return false;
}
if (stack.pop() != pairs.get(c)) {
    return false;
}

Mistake 2: Forgetting to check stack at end

// ❌ WRONG - Accepts "((" as valid
return true;

// βœ“ CORRECT - All must be matched
return stack.isEmpty();

Mistake 3: Wrong bracket matching logic

// ❌ WRONG - Checks if closing == opening
if (stack.pop() == c) {  // '(' == ')'? Never true!

// βœ“ CORRECT - Use map or push expected
if (stack.pop() != pairs.get(c)) {

Mistake 4: Using wrong data structure

// ❌ WRONG - Need LIFO, not FIFO
Queue<Character> queue = new LinkedList<>();

// βœ“ CORRECT - Stack for LIFO
Stack<Character> stack = new Stack<>();

Mistake 5: Not handling all bracket types

// ❌ WRONG - Only handles ()
if (c == '(') stack.push(c);

// βœ“ CORRECT - Handle all three types
if (c == '(' || c == '{' || c == '[') {
    stack.push(c);
}


🎯 Pattern Recognition

Problem Type: Matching Pairs (Parentheses)
Core Pattern: Stack Pattern 1 - Matching Pairs

When to Apply:
βœ“ Validate matching brackets/parentheses
βœ“ Check balanced delimiters
βœ“ Verify proper nesting
βœ“ Match opening/closing tags

Recognition Keywords:
- "valid"
- "balanced"
- "parentheses"
- "brackets"
- "matching"
- "proper order"

Similar Problems:
- Generate Parentheses (LC 22)
- Minimum Add to Make Valid (LC 921)
- Longest Valid Parentheses (LC 32) - Hard
- Remove Invalid Parentheses (LC 301) - Hard

Key Components:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Stack: Track opening brackets             β”‚
β”‚ Push: When see opening bracket            β”‚
β”‚ Pop: When see closing bracket             β”‚
β”‚ Validate: Pop matches closing type        β”‚
β”‚ Final: Stack must be empty                β”‚
β”‚ Time: O(n), Space: O(n)                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧠 Interview Strategy

Step 1: "I recognize this as a matching pairs problem"
Step 2: "I'll use a stack to track opening brackets"
Step 3: "Push opening, pop and match closing"

Key Points to Mention:
- Stack provides LIFO for most recent matching
- Three checks: empty stack, correct type, final empty
- HashMap maps closing to opening brackets
- Alternative: push expected closing
- Time: O(n), Space: O(n)

Walk Through Example:
"For '({[]})'':
 '(' β†’ Push to stack
 '{' β†’ Push to stack
 '[' β†’ Push to stack
 ']' β†’ Pop '[', matches βœ“
 '}' β†’ Pop '{', matches βœ“
 ')' β†’ Pop '(', matches βœ“
 Stack empty β†’ Valid!"

Why Stack:
"Need to match most recent opening bracket.
 This is LIFO behavior = Stack.
 If we used queue (FIFO), we'd match oldest
 opening bracket, which is wrong."

Edge Cases to Mention:
βœ“ Empty string β†’ valid (edge case depends on requirement)
βœ“ Only opening brackets β†’ invalid (stack not empty)
βœ“ Only closing brackets β†’ invalid (empty stack on close)
βœ“ Wrong order β†’ invalid (mismatch)
βœ“ Single character β†’ invalid (can't be matched)

πŸ“ Quick Revision Notes

🎯 Core Concept:

Valid Parentheses: Use stack to track opening brackets. Push opening brackets ( { [. When see closing ) } ], check stack not empty and pop matches. At end, stack must be empty (all matched). Use HashMap to map closing→opening for clean code.

⚑ Quick Implementation:

import java.util.HashMap;
import java.util.Stack;

class Solution {
  public boolean isValid(String s) {
    // // Approach 1 - using hashmap
    // // Store corresponding chars.
    // HashMap<Character, Character> map = new HashMap<>();
    // map.put(')', '(');
    // map.put(']', '[');
    // map.put('}', '{');

    // Stack<Character> stack = new Stack<>();
    // for(char c : s.toCharArray()) {
    //   if(!map.containsKey(c)) {
    //     stack.push(c);
    //   } else {
    //     if(stack.pop() != map.get(c)) {
    //       return false;
    //     }
    //   }
    // }

    // return stack.isEmpty();

    // Approach 2 - using stack alone (little clever)
    Stack<Character> stack = new Stack<>();
    for(char c : s.toCharArray()) {
      switch (c) {
        case '(':
          stack.push(')');
          break;

        case '[':
          stack.push(']');
          break;

        case '{':
          stack.push('}');
          break;

        default:
          if(stack.isEmpty() || stack.pop() != c) {
            return false;
          }
          break;
      }
    }

    return stack.isEmpty();
  }

  public static void main(String[] args) {
    Solution s = new Solution();

    System.out.println(s.isValid("()"));
    System.out.println(s.isValid("()[]{}"));
    System.out.println(s.isValid(""));
    System.out.println(s.isValid("("));
    System.out.println(s.isValid("(]"));
  }
}

πŸ”‘ Key Insights:

  • Pattern: Stack Pattern 1 (Matching Pairs)
  • LIFO: Most recent opening matches first closing
  • Three checks: empty before pop, correct type, empty at end
  • HashMap: Clean way to map closingβ†’opening
  • Alternative: Push expected closing bracket
  • Time: O(n), Space: O(n) βœ“

πŸŽͺ Memory Aid:

"Open pushes, close pops and matches, end checks empty!"
"Push opening, pop matching closing, verify empty!" ✨

⚠️ Critical Checks:

Three essential checks:

1. Before popping:
   if (stack.isEmpty()) return false;

2. After popping:
   if (popped != expected) return false;

3. At the end:
   return stack.isEmpty();

Miss any of these β†’ Wrong answer!

πŸ§ͺ Edge Cases

Case 1: Empty string

Input: s = ""
Output: true (or depends on requirement)
Handled: Loop doesn't run, stack empty

Case 2: Single character

Input: s = "("
Output: false
Handled: Stack not empty at end

Case 3: Only opening

Input: s = "((("
Output: false
Handled: Stack not empty at end

Case 4: Only closing

Input: s = ")))"
Output: false
Handled: Stack empty on first pop attempt

Case 5: Wrong order

Input: s = "([)]"
Output: false
Handled: Pop '[' but expect '(' for ')'

Case 6: Nested valid

Input: s = "{[()]}"
Output: true
Handled: All brackets match in correct order

All handled perfectly! βœ“


πŸŽ“ Complexity Analysis

Time Complexity: O(n)

Single pass through string:
  for (char c : s.toCharArray())  // n iterations

Per iteration:
  - HashMap lookup: O(1)
  - Stack push: O(1)
  - Stack pop: O(1)

Total: O(n) βœ“

Space Complexity: O(n)

Stack space:
  Worst case: all opening brackets
  Example: "((((("
  Stack stores n/2 elements

HashMap space:
  Only 3 entries (constant)
  O(1)

Total: O(n) for stack βœ“

πŸ”„ Variations & Extensions

Extension 1: Custom Bracket Types

// Easy to extend!
Map<Character, Character> pairs = Map.of(
    ')', '(',
    '}', '{',
    ']', '[',
    '>', '<',  // Add custom
    'Β»', 'Β«'   // Add custom
);

Extension 2: With Other Characters

// Ignore non-bracket characters
for (char c : s.toCharArray()) {
    if (c == '(' || c == '{' || c == '[') {
        stack.push(c);
    } else if (c == ')' || c == '}' || c == ']') {
        // Check matching
    }
    // else: ignore other characters
}

Extension 3: Return Error Position

public int findInvalidPosition(String s) {
    Stack<Character> stack = new Stack<>();

    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        // ... matching logic
        if (invalid) {
            return i;  // Return position
        }
    }

    return stack.isEmpty() ? -1 : stack.size();
}

Same Pattern (Easier): - Remove Outermost Parentheses (LC 1021)

Same Pattern (Harder): - Generate Parentheses (LC 22) - Generate valid combinations - Minimum Add to Make Valid (LC 921) - Count missing brackets - Valid Parenthesis String (LC 678) - With wildcards

Advanced: - Longest Valid Parentheses (LC 32) - Hard, find longest valid - Remove Invalid Parentheses (LC 301) - Hard, BFS/DFS


Happy practicing! 🎯

Note: This is THE foundational stack problem! Master this and you understand: (1) LIFO behavior, (2) matching pairs pattern, (3) stack usage, (4) validation logic. This pattern appears in dozens of problems. It's asked at Amazon, Meta, Google, Apple constantly. The "push opening, pop matching closing" pattern is fundamental! πŸ’ͺ✨