20. Valid Parentheses
- Pattern:
- stack matching
- Time:
- O(n)
- Space:
- O(n)
- Written:
Problem
()[]{} characters, decide whether every bracket is closed by the same type of bracket, in the right order.
Approach
Map<closer, matching-opener> — pop on match, return false on mismatch or empty stack; after the loop, valid only if stack is empty.
Solution
class Solution {
public boolean isValid(String s) {
Map<Character, Character> parentheses = new HashMap<>();
Deque<Character> stack = new ArrayDeque<>();
parentheses.put(')', '(');
parentheses.put('}', '{');
parentheses.put(']', '[');
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) {
return false;
}
if (parentheses.get(c).equals(stack.peek())) {
stack.pop();
} else {
return false;
}
}
}
return stack.isEmpty();
}
}Mistakes
Typo, wouldn't compile: wrote pathenteses.get(c)instead ofparentheses.get(c)— mismatched variable name. Compiler error (cannot find symbol), not a logic bug. Reminder: this is why you always compile-check before you're "done," typos hide easily in your own code.Used ==instead of.equals()on boxedCharacterobjects:parentheses.get(c) == stack.peek().Map.get()andDeque.peek()both returnCharacterobjects (autoboxed), so==compares references, not values. This happened to work only becauseCharactercaches ALL values 0–127 (full ASCII range) per the JLS — so it was accidentally correct, not correctly reasoned. Would silently break with a type where the cache range is smaller (e.g.Integer, cached only -128 to 127).
Takeaways
Concept to re-drill
== vs .equals()) — I know the rule in theory but still reached for == under pressure while focused on the stack logic. This is the single most important trap to build instinct against, since it produces code that looks correct and runs correct on small test cases, which makes it dangerous.
What I got right without help
Push/pop stack mechanics Empty-stack guard BEFORE peeking (this is the defensive-indexing habit from Ch.6) Correct final check ( stack.isEmpty()) to catch unclosed openersChose ArrayDequeover legacyStackcorrectly
==/.equals() slip is the thing to actively watch for next time, not the pattern itself.
Complexity
Time: O(n) — one pass over the string. Space: O(n) — the stack, when every character is an opener.
Re-quiz
Can I write it from memory without looking? Why does ==on twoCharactervalues from aMapand aDequehappen to work here, and when would it break?