Valid Parentheses is the classic introduction to the stack — the data structure built for "match the most recent thing first." Once you see how a stack solves it, you will spot the same shape in dozens of other problems.
Problem. Given a string s made only of the brackets ()[]{}, decide whether it is validly
matched. Every opening bracket must be closed by the same type of bracket, and brackets must
close in the right order (the most recently opened one closes first).
Example: s = "([])" → True. But s = "([)]" → False (the ) tries to close a [).
The idea
When you see a closing bracket, which opener should it match? Always the most recent one still open. That "most recent, not yet closed" rule is exactly what a stack gives you: last in, first out.
So we walk the string once:
- An opening bracket? Push it onto the stack — it is now "waiting to be closed."
- A closing bracket? Look at the top of the stack. If it is the matching opener, pop it (that pair is done). If the stack is empty, or the top is the wrong type, the string is invalid.
At the very end, the stack must be empty. If anything is left over, some opener was never closed.
A closing bracket with the wrong opener on top (or no opener at all) fails immediately. That is what catches "([)]": when ) arrives, the top of the stack is [, not (, so we return False.
Walk through it
Step through the animation on "([])". The pointer i scans left to right. The strip on the right is the stack. We push (, then [. When ] arrives, the top is [ — match, so we pop. When ) arrives, the top is ( — match, so we pop. The stack ends empty, so the answer is True.
Pseudocode
make an empty stack
for each character ch in s:
if ch is an opening bracket:
push ch onto the stack
else: # ch is a closing bracket
if stack is empty: return False # nothing to close
top = pop the stack
if top is not the match for ch: return False
return (stack is empty) # leftovers mean an opener was never closedThe whole algorithm is one pass, and every decision looks only at the top of the stack.
The Python solution
def is_valid(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch not in pairs:
stack.append(ch)
elif not stack or stack.pop() != pairs[ch]:
return False
return not stackpairsmaps each closing bracket to the opener it needs. Anything not inpairsis therefore an opener.if ch not in pairs:—chis an opening bracket, so weappend(push) it.- Line 7 handles closers and does two checks at once:
not stackmeans there is no opener to match (fail), andstack.pop() != pairs[ch]pops the top and fails if it is the wrong type. return not stack—Trueonly if the stack ended empty. Any leftover opener means it was never closed.
Complexity
| Case | Time | Notes |
|---|---|---|
| Scan the string | O(n) (moderate) | one pass, O(1) work per char |
O(n) (moderate)Each character is pushed and popped at most once, so the time is O(n). The stack can hold up to n openers (think "((((("), so the extra space is O(n).
When this pattern shows up
Any time you need to match things in nested, last-opened-first-closed order, reach for a stack. Valid Parentheses, "min stack," "evaluate reverse-Polish notation," "remove adjacent duplicates," and matching HTML/XML tags are all the same move: push as you go, pop when the current item resolves the top.
Two easy ways to get this wrong: forgetting to check that the stack is non-empty before popping (a
string like ")" would crash or mis-answer), and forgetting the final empty-stack check (a string
like "(" never hits a closing bracket, so you must reject it at the end).
Practice
For s = '([)]', what happens when the scan reaches the ')' character, and what does the function return?
1. Why is a stack the right data structure for this problem?
2. After scanning the whole string, when is it valid?
3. Why must we check that the stack is non-empty before popping on a closing bracket?
4. What does the dict 'pairs' map, in the given solution?