21.Valid Parentheses
Problem
Given a string s containing only the characters '(', ')', '{', '}', '[' and ']', decide whether it is valid:
- Every open bracket is closed by a bracket of the same type.
- Open brackets are closed in the correct order (the most recently opened must close first).
- Every close bracket has a matching open bracket.
Examples
Input: s = "()[]{}"
Output: trueInput: s = "([)]"
Output: false
Explanation: '[' is opened after '(' so it must close first, but ')' arrives first.Input: s = "{[]}"
Output: true
Explanation: Brackets are properly nested.Constraints
1 <= s.length <= 10^4sconsists of parentheses only:'()[]{}'.
๐ก Key Insight
"The most recently opened bracket must be closed first" is exactly last-in, first-out. Push each opening bracket onto a stack; each closing bracket must match the top of the stack. The string is valid iff every closer matches and the stack is empty at the end.
Brute Force (Repeated Removal)
TimeO(nยฒ)SpaceO(n)Intuition
In any valid string, at least one adjacent pair "()", "[]" or "{}" exists (the innermost pair). Removing it leaves another valid string. So keep deleting adjacent pairs until nothing changes; the string was valid iff it becomes empty.
Algorithm
- Repeat:
- Replace every
"()","[]","{}"with"". - If the length did not change, stop.
- Replace every
- Return
s.isEmpty().
Visualization
"{[()]}" โ remove "()" โ "{[]}"
"{[]}" โ remove "[]" โ "{}"
"{}" โ remove "{}" โ "" โ true
"([)]" โ no adjacent pair โ stuck, not empty โ falseCode
class Solution {
public boolean isValid(String s) {
int before;
do {
before = s.length();
// strip every innermost matching pair
s = s.replace("()", "").replace("[]", "").replace("{}", "");
} while (s.length() != before);
return s.isEmpty();
}
}Complexity
- Time: O(nยฒ) โ up to
n / 2rounds, each rebuilding a string of length up ton. - Space: O(n) โ new strings are created on every replace.
Stack + Closer-to-Opener Map
TimeO(n)SpaceO(n)Intuition
Scan left to right. Opening brackets are "pending" โ push them. When a closing bracket appears, it must close the most recent pending opener, which is the top of the stack. A map from each closer to its opener makes the comparison simple.
Algorithm
- Map
')' โ '(',']' โ '[','}' โ '{'. - For each
cins:- If
cis a closer: if the stack is empty orpop() != map.get(c), returnfalse. - Otherwise push
c.
- If
- Return
stack.isEmpty().
Dry Run
s = "{[]}"
'{' push stack: {
'[' push stack: { [
']' pop '[' โ stack: {
'}' pop '{' โ stack: (empty) โ trueCode
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
class Solution {
public boolean isValid(String s) {
Map<Character, Character> openerOf = new HashMap<>();
openerOf.put(')', '(');
openerOf.put(']', '[');
openerOf.put('}', '{');
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (openerOf.containsKey(c)) {
// closer must match the most recent unmatched opener
if (stack.isEmpty()) {
return false; // closer with nothing to close
}
char top = stack.pop(); // unbox before comparing
if (top != openerOf.get(c)) {
return false;
}
} else {
stack.push(c);
}
}
return stack.isEmpty(); // leftover openers are unclosed
}
}Complexity
- Time: O(n) โ each character is pushed/popped at most once.
- Space: O(n) โ e.g.
"(((((("pushes every character.
Stack of Expected ClosersOptimal
TimeO(n)SpaceO(n)Intuition
Flip the bookkeeping: when we see an opener, push the closer we expect later. Then a closing bracket is correct iff it equals the popped value โ no map, no branching on bracket type. A char[] used as a stack avoids boxing, and an odd-length string can be rejected immediately.
Algorithm
- If
nis odd, returnfalse. stack = new char[n],top = 0.- For each
c:'('โ push')';'['โ push']';'{'โ push'}'.- Otherwise (a closer): if
top == 0orstack[--top] != c, returnfalse.
- Return
top == 0.
Dry Run
flowchart TD
A["s = '([)]'"] --> B["'(' โ push ')'<br/>stack: )"]
B --> C["'[' โ push ']'<br/>stack: ) ]"]
C --> D["')' โ pop ']'"]
D --> E{"']' == ')'?"}
E -->|"no"| F["return false"]Code
class Solution {
public boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) {
return false; // brackets come in pairs
}
char[] stack = new char[n];
int top = 0;
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (c == '(') {
stack[top++] = ')'; // remember which closer we now expect
} else if (c == '[') {
stack[top++] = ']';
} else if (c == '{') {
stack[top++] = '}';
} else if (top == 0 || stack[--top] != c) {
return false; // unexpected closer
}
}
return top == 0;
}
}Complexity
- Time: O(n) โ one pass with O(1) work per character.
- Space: O(n) โ the stack array.
Complexity Comparison
| Approach | Time | Space |
|---|---|---|
| Brute Force (repeated removal) | O(nยฒ) | O(n) |
| Stack + Map | O(n) | O(n) |
| Stack of Expected Closers | O(n) | O(n) |
Common Pitfalls
- Counting brackets per type โ
"([)]"has balanced counts but invalid order; you need a stack. - Popping from an empty stack on a leading closer like
")("โ check emptiness first. - Returning
trueat the end without checking the stack is empty โ"(("would wrongly pass. - Comparing two
Characterobjects with==/!=โ that compares references; unbox one side tocharfirst. - Using
java.util.Stackโ it works but is synchronized and slower; preferArrayDequeor a plain array.