Valid Parentheses
Problem
s contains only the bracket characters
( ) [ ] { }. Decide whether it is well-formed: every opening bracket is
closed by a bracket of the same type, and brackets close in the reverse order
they were opened (properly nested)."([]{})"Outputtrue"(]"Outputfalse"([)]"Outputfalse1 ≤ s.length ≤ 104 · s consists only of ()[]{}
Try it yourself
class Solution {
public boolean isValid(String s) {
}
}
Instinct Things resolve in reverse order of arrival (brackets, adjacent pairs, nested expressions) - push now, pop when its partner shows up.
Memory hack Last in, first out - like a pile of plates, you only ever touch the one on top; every closer pops its opener.
Open the Stack guide →Approaches
Brute ForceRepeatedly Delete Adjacent Pairs
O(n2)Space O(n)Any valid string contains an innermost pair (), [] or
{} side by side. Delete all such pairs and repeat until nothing changes;
the string was valid iff it shrinks to empty.
public boolean isValid(String s) { String prev; do { prev = s; s = s.replace("()", "").replace("[]", "").replace("{}", ""); } while (!s.equals(prev)); return s.isEmpty(); }
Cute and short, but up to n/2 rounds of O(n) string rebuilding.
BetterStack + Closer→Opener Map
O(n)Space O(n)Push every opening bracket. On a closing bracket, the stack top must be its partner (looked up in a map); otherwise fail. At the end the stack must be empty, or some bracket was never closed.
public boolean isValid(String s) { Map<Character, Character> open = Map.of(')', '(', ']', '[', '}', '{'); Deque<Character> stack = new ArrayDeque<>(); for (char c : s.toCharArray()) { if (!open.containsKey(c)) stack.push(c); // opening bracket else if (stack.isEmpty() || !open.get(c).equals(stack.pop())) return false; } return stack.isEmpty(); }
Linear and clear; boxing Characters and map lookups add overhead.
OptimalArray Stack of Expected Closers
O(n)Space O(n)On an opener, push the closer we expect; then a closing char just has to
equal the popped value — no map needed. A primitive char[] stack
avoids boxing, and an odd length is rejected instantly.
public boolean isValid(String s) { if (s.length() % 2 == 1) return false; char[] stack = new char[s.length()]; int top = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '(') stack[top++] = ')'; // push expected closer else if (c == '[') stack[top++] = ']'; else if (c == '{') stack[top++] = '}'; else if (top == 0 || stack[--top] != c) return false; } return top == 0; }
Dry run"([]{})"
- i=0(i[]{})push ) → stack: )
- i=1([i]{})push ] → stack: ) ]
- i=2([]i{})pop ] matches → stack: )
- i=3([]{i})push } → stack: ) }
- i=4([]{}i)pop } matches → stack: )
- i=5([]{})ipop ) matches, empty ✔
Same Big-O, but single pass with no allocations per char — fastest in practice.
At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Repeatedly Delete Adjacent Pairs | O(n2) | O(n) |
| Better | Stack + Closer→Opener Map | O(n) | O(n) |
| Optimal | Array Stack of Expected Closers | O(n) | O(n) |
- Only counting brackets —
"([)]"has balanced counts but is invalid. - Popping an empty stack on a leading closer like
")". - Returning
truewithout checking the stack is empty —"(("fails.
- Explain why LIFO matches nesting: the last opened must be the first closed.
- Mention the odd-length early exit as a free optimisation.
- Compare two
Characterobjects withequals, never!=.