Difficulty: Easy Pattern: Stack LeetCode: https://leetcode.com/problems/valid-parentheses/
Given a string s containing just the characters '(', ')', '{', '}',
'[' and ']', determine if the input string is valid.
A string is valid when:
Signature:
boolean isValid(String s)
Examples (verbatim from LeetCode):
Input: s = "()"
Output: true
Input: s = "()[]{}"
Output: true
Input: s = "(]"
Output: false
Think of nested Russian dolls, or a stack of boxes you tape shut before opening
the next: the box you opened last must be the first one you close. Brackets
behave the same way — if you write ( then [, the matching ] must come
before the ). This “last-in, first-out” rule is called LIFO, and a
stack is the data structure built for it:
you add and remove only at the top, like a stack of plates.
Trace the smallest case that actually shows this, s = "([])":
( — opener, put it on the stack. Stack (bottom to top): (.[ — opener, put it on top. Stack: ( [.] — closer. It must match the most recent opener, the top [. They
match, so remove the [. Stack: (.) — closer, must match the top (. Match, remove it. Stack: empty.It worked because each closer grabbed the opener right beneath it. That is the
whole idea: openers wait on the stack; a closer always pairs with the top
opener. If the top is the wrong type, or the stack is empty when a closer
arrives (nothing to match), the string is invalid. And after reading every
character the stack must be empty — a leftover opener like the lone ( in "("
never got closed, so that is invalid too.
Pause and answer before expanding. Wrong guesses teach more than fast right ones.
Q1 (recall). When a closer (), ], }) arrives, which opener does it try to match against?
Q2 (comprehend). Trace s = "()". After both characters are read, what is on the stack and what is returned?
(; returns falsefunction isValid(s):
create an empty stack
for each character c in s:
if c is an opener ('(' , '[' , '{'):
push c onto the stack
else: # c is a closer
if stack is empty:
return false # nothing to match against
open = pop the top of the stack
if open and c are not a matching pair:
return false
return true if the stack is empty, else false
import java.util.*;
class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) {
return false;
}
char open = stack.pop();
if (!matches(open, c)) {
return false;
}
}
}
return stack.isEmpty();
}
private boolean matches(char open, char close) {
return (open == '(' && close == ')')
|| (open == '[' && close == ']')
|| (open == '{' && close == '}');
}
}
We use Deque<Character> backed by ArrayDeque because it is the modern, faster
LIFO container (the legacy Stack class is synchronized and discouraged). Openers
are pushed immediately with no checks. Closers must check two failure modes: an
empty stack (closer with no opener) and a type mismatch. The matches helper
pairs each opener with exactly one closer so the logic reads as a table rather
than nested ifs. Finally we return stack.isEmpty() rather than true, because
a leftover opener like "(" otherwise passes every closer check yet is still
invalid.
Time: O(n) -- each character is pushed and popped at most once.
Space: O(n) -- in the worst case (all openers) the stack holds every character.
Step-by-step on s = "([)]" (expected false):
| Step | char | action | stack (bottom -> top) | result so far |
|---|---|---|---|---|
| 1 | ( |
opener -> push | [( |
— |
| 2 | [ |
opener -> push | [([ |
— |
| 3 | ) |
closer, pop top = [ |
[( |
[ vs ) mismatch -> return false |
The algorithm stops at step 3 and returns false. Notice the top of the stack
[ is not the partner of ), which is exactly the violation the rules describe.
Dry-run on s = "{[]}" (expected true) for contrast:
| Step | char | action | stack (bottom -> top) |
|---|---|---|---|
| 1 | { |
push | { |
| 2 | [ |
push | { [ |
| 3 | ] |
pop [, matches -> ok |
{ |
| 4 | } |
pop {, matches -> ok |
(empty) |
End: stack empty -> return true.
Q1 (apply). Trace s = ")" (a single closing bracket). What happens and what is returned?
( then returns true at the endQ2 (analyze). What does isValid("") (the empty string) return, and why?
return stack.isEmpty(), which is trueQ3 (transfer). Suppose a fourth bracket pair < and > were added to the language. What is the smallest change to the solution?
true at the end unconditionally. You must return stack.isEmpty()
— a single "(" matches no closer but never triggers a mismatch either.pop / peek on an empty stack before the guard. Always check
isEmpty() first; otherwise the closer of ")" on an empty input throws.char values with == is fine, but checking String equality on
single-character substrings needs .equals(...). Prefer charAt so you stay
in char territory.matches table avoids tangled if/else chains.