Valid Parentheses

Easy

A stack matches each closer to the most recent opener.

Stack Trace

The stack of open brackets starts empty.
StopStackExport
s
  1. (0
  2. [1
  3. {2
  4. }3
  5. ]4
  6. )5
stack
  1. empty
  • CH1 c

Problem

Given a string containing only parentheses and square or curly brackets, determine whether its brackets form a valid sequence. Every closing bracket must match the most recently opened bracket, and no opening bracket may remain unmatched at the end.

Examples

Input: "([{}])"

Output: true

Each closer matches the most recent opener: {} first, then [], then ().

Input: "([)]"

Output: false

The counts balance, but ) arrives while [ is the most recent unmatched opener.

The Goal

Return True if every bracket in s is closed by the right type, in the right order, with nothing left open.

([{}]) is valid: } closes {, then ] closes [, then ) closes (. ([)] has one of each pair but isn't valid: ) comes while [ is still open.

Counting can't tell these apart. You have to know which opener is the latest one still open.

Close the latest first

Each closer matches the latest opener still open, and no opener is left at the end.

Type and order both matter.

Key Idea: The Latest Opener Closes First

Brackets nest, so the opener that came last has to close first. That's a stack: push each opener, and a closer has to match the top.

A match pops the top, and the opener under it is next in line. A closer that doesn't match the top, or comes when the stack is empty, settles it: the string is invalid.

A closer must close the top. A match pops it.

With [ on top, ] matches and ) doesn't.

The Algorithm

python
def is_valid(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for c in s:
        if c not in pairs:
            stack.append(c)
        elif not stack or stack[-1] != pairs[c]:
            return False
        else:
            stack.pop()
    return not stack

pairs maps each closer to the opener it closes. Check that the stack isn't empty before reading its top, and return False at the first bad closer. The last line catches openers that never closed.

ApproachChecks typeChecks orderOn ([)]
Count each typeYesNoTrue, which is wrong
Stack of openersYesYesFalse

Walkthrough

Run it on ([{}]).

The three openers go on the stack, so { ends up on top. Then each closer finds its match there: } pops {, ] pops [, and ) pops (. The stack ends empty, so the answer is True.

Try Break it. On ([)], the stage stops at ) with [ on top and returns False.

Checkpoint · predict the next step

Step 6 of 17 on the stage: Read '{', an opener.

c on "{" (index 2) · stack = ["(", "["]

What does step 7 show?
Step through it on the stage

Pitfalls

  • Check for an empty stack before reading its top. )( fails at its first character.
  • Check the type, not that something is open. When ) comes in ([)], the stack isn't empty, but its top is [.
  • Don't return True because the scan finished. ((() ends with two openers still on the stack.
  • An empty string is valid: nothing is left open.

Why is ([)] invalid?

Complexity

Each character is read once, and each opener is pushed once and popped at most once, so time is O(n).

The stack can hold every character, as in ((((, so space is O(n). There are only six kinds of bracket, but the stack can still grow with n, so the space isn't O(1).

One scan, one stack

Time
O(n)
Space
O(n)
Where the time goes
  • Scan the stringEach character once.dominantO(n)
  • Push and popEach opener goes on once and off at most once.O(1)
  • The stackEvery character, at worst.dominantO(n)
How it grows
input size n →opsO(n²)O(n log n)O(n)O(log n)O(1)
Roughly what that costs
n101001,00010,000
O(n)101001K10K

One pass, with a stack as deep as the nesting.

In short

Push every opening bracket. On a closing bracket, the top of the stack must be its matching opener — otherwise it's invalid. After the scan the stack must be empty. The stack naturally captures the last-opened-first-closed nesting.

TimeO(n)SpaceO(n)

Common mistake

Only counting brackets rather than matching types/order — '([)]' has balanced counts but is invalid.