20. Valid Parentheses - Explanation

Problem Link

Description

You are given a string s consisting of the following characters: '(', ')', '{', '}', '[' and ']'.

The input string s is valid if and only if:

  1. Every open bracket is closed by the same type of close bracket.
  2. Open brackets are closed in the correct order.
  3. Every close bracket has a corresponding open bracket of the same type.

Return true if s is a valid string, and false otherwise.

Example 1:

Input: s = "[]"

Output: true

Example 2:

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

Output: true

Example 3:

Input: s = "[(])"

Output: false

Explanation: The brackets are not closed in the correct order.

Constraints:

  • 1 <= s.length <= 1000


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(n) time and O(n) space, where n is the length of the given string.


Hint 1

A brute force solution would be to continuously remove valid brackets until no more can be removed. If the remaining string is empty, return true; otherwise, return false. This would result in an O(n^2) solution. Can we think of a better approach? Perhaps a data structure could help.


Hint 2

We can use a stack to store characters. Iterate through the string by index. For an opening bracket, push it onto the stack. If the bracket is a closing type, check for the corresponding opening bracket at the top of the stack. If we don't find the corresponding opening bracket, immediately return false. Why does this work?


Hint 3

In a valid parenthesis expression, every opening bracket must have a corresponding closing bracket. The stack is used to process the valid string, and it should be empty after the entire process. This ensures that there is a valid substring between each opening and closing bracket.


Company Tags


Prerequisites

Before attempting this problem, you should be comfortable with:

  • Stack Data Structure - Understanding LIFO (Last-In-First-Out) operations for push and pop
  • Hash Maps - Using dictionaries to map closing brackets to their corresponding opening brackets
  • String Traversal - Iterating through characters in a string one by one

1. Brute Force

Intuition

The idea is simple:
valid parentheses must always appear in matching pairs like "()", "{}", or "[]".
So if the string is valid, we can repeatedly remove these matching pairs until nothing is left.
If, after removing all possible pairs, the string becomes empty, then the parentheses were properly matched.
Otherwise, some unmatched characters remain, meaning the string is invalid.

Algorithm

  1. While the string still contains "()", "{}", or "[]":
    • Remove all occurrences of those pairs.
  2. After no more pairs can be removed:
    • If the string is empty, return true.
    • Otherwise, return false.
Example - Dry Run

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

We repeatedly remove matching pairs (), {}, [] until no more can be removed.

Initial String:
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘
  0   1   2   3   4   5


Iteration 1:
    Remove "()" → not found at adjacent positions
    Remove "{}" → not found at adjacent positions
    Remove "[]" → found at positions 2-3!

             Found pair
               ↓↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

    After removal:
    ┌───┬───┬───┬───┐
    │ ( │ { │ } │ ) │
    └───┴───┴───┴───┘
      0   1   2   3


Iteration 2:
    Remove "()" → not found at adjacent positions
    Remove "{}" → found at positions 1-2!

         Found pair
           ↓↓
    ┌───┬───┬───┬───┐
    │ ( │ { │ } │ ) │
    └───┴───┴───┴───┘

    After removal:
    ┌───┬───┐
    │ ( │ ) │
    └───┴───┘
      0   1


Iteration 3:
    Remove "()" → found at positions 0-1!

     Found pair
       ↓↓
    ┌───┬───┐
    │ ( │ ) │
    └───┴───┘

    After removal:
    ┌───┐
    │   │  ← Empty string!
    └───┘


Final Result:
    String is empty → return True

class Solution:
    def isValid(self, s: str) -> bool:
        while '()' in s or '{}' in s or '[]' in s:
            s = s.replace('()', '')
            s = s.replace('{}', '')
            s = s.replace('[]', '')
        return s == ''

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n)O(n)

2. Stack

Intuition

Valid parentheses must follow a last-opened, first-closed order — just like stacking plates.
So we use a stack to track opening brackets.
Whenever we see a closing bracket, we simply check whether it matches the most recent opening bracket on top of the stack.
If it matches, we remove that opening bracket.
If it doesn't match (or the stack is empty), the string is invalid.
A valid string ends with an empty stack.

Algorithm

  1. Create a stack to store opening brackets.
  2. For each character c in the string:
    • If it is an opening bracket, push it onto the stack.
    • If it is a closing bracket:
      • Check if the stack is not empty and its top matches the corresponding opening bracket.
      • If yes, pop the stack.
      • Otherwise, return false.
  3. After processing all characters:
    • If the stack is empty, return true.
    • Otherwise, return false.
Example - Dry Run

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

We use a stack to track opening brackets and match them with closing brackets.

String (processing left to right):
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘
  0   1   2   3   4   5


Step 1: char = '('

    Current char
         ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: '(' is an opening bracket → Push to stack

Stack:
┌───┐
│ ( │ ← top
└───┘


Step 2: char = '{'

        Current char
             ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: '{' is an opening bracket → Push to stack

Stack:
┌───┐
│ { │ ← top
├───┤
│ ( │
└───┘


Step 3: char = '['

            Current char
                 ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: '[' is an opening bracket → Push to stack

Stack:
┌───┐
│ [ │ ← top
├───┤
│ { │
├───┤
│ ( │
└───┘


Step 4: char = ']'

                Current char
                     ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: ']' is a closing bracket
        Top of stack is '[' which matches! → Pop from stack

Stack:
┌───┐
│ { │ ← top
├───┤
│ ( │
└───┘


Step 5: char = '}'

                    Current char
                         ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: '}' is a closing bracket
        Top of stack is '{' which matches! → Pop from stack

Stack:
┌───┐
│ ( │ ← top
└───┘


Step 6: char = ')'

                        Current char
                             ↓
┌───┬───┬───┬───┬───┬───┐
│ ( │ { │ [ │ ] │ } │ ) │
└───┴───┴───┴───┴───┴───┘

Action: ')' is a closing bracket
        Top of stack is '(' which matches! → Pop from stack

Stack:
┌───┐
│   │ ← empty
└───┘


Final Result:
    Stack is empty → All brackets matched correctly!
    Return: True

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        closeToOpen = { ")" : "(", "]" : "[", "}" : "{" }

        for c in s:
            if c in closeToOpen:
                if stack and stack[-1] == closeToOpen[c]:
                    stack.pop()
                else:
                    return False
            else:
                stack.append(c)

        return True if not stack else False

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Checking Stack Empty Before Popping

When encountering a closing bracket, you must verify the stack is not empty before checking the top element. Attempting to pop from an empty stack causes an error.

# Wrong: crashes on empty stack
if stack[-1] == closeToOpen[c]:
# Correct: check stack first
if stack and stack[-1] == closeToOpen[c]:

Forgetting to Check if Stack is Empty at the End

After processing all characters, some opening brackets might remain unmatched. The string "(()" processes without errors but leaves "(" on the stack, making it invalid.

Mixing Up Opening and Closing Brackets

When building the bracket mapping, ensure closing brackets map to their corresponding opening brackets, not the other way around. The lookup should happen when you encounter a closing bracket.

# Correct mapping: closing -> opening
closeToOpen = {")": "(", "]": "[", "}": "{"}

Using Wrong Data Structure

Using a queue instead of a stack fails because parentheses follow LIFO (last-in, first-out) order. The most recent opening bracket must be closed first, which requires stack behavior.