
第一步:想什么数据结构适合解决这个问题?
我先想:括号的闭合是「后进先出 」的顺序 ------ 最后出现的左括号,必须最先被闭合。比如 {[()]}:
- 左括号顺序:
{→[→( - 右括号顺序:
)→]→} - 正好是「最后进来的
(第一个出去」,完美符合 ** 栈(Stack)** 的特性!
那栈的作用就明确了:
- 遇到左括号:压入栈中(存起来,等对应的右括号来匹配)
- 遇到右括号:必须和栈顶的左括号匹配,匹配成功就弹出栈顶;匹配失败直接返回无效
第二步:先想边界条件,再写核心逻辑
1. 最容易想到的优化:长度为奇数直接返回 false
括号必须成对出现,所以如果字符串长度是奇数,100% 无效,直接 return false,不用再遍历了,省时间。对应代码第 4-6 行:
java
if (n % 2 == 1) {
return false;
}
2. 怎么快速判断右括号和左括号是否匹配?
我需要一个映射:右括号 → 对应的左括号,这样遇到右括号时,直接查它该匹配哪个左括号,不用写一堆 if-else。比如:
)→(]→[}→{用 HashMap 存这个映射最方便,对应代码第 8-13 行:
java
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
3. 核心遍历逻辑
我要遍历字符串的每一个字符 ch:
情况 1:ch 是右括号(在 HashMap 的 key 里)
- 先判断:如果栈是空的(说明没有左括号和它匹配,比如第一个字符就是
)),或者栈顶元素不是对应的左括号(比如栈顶是[,当前是)),直接 return false - 如果匹配成功:把栈顶的左括号弹出(表示这对括号闭合了)
情况 2:ch 是左括号(不在 HashMap 的 key 里)
- 直接压入栈中,等后面的右括号来匹配
对应代码第 15-28 行:
java
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
// 若当前字符为 右括号
if (pairs.containsKey(ch)) {
// 若栈内为空 或 栈顶元素(左括号) 不是与当前右括号匹配的类型,直接返回false
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
// 匹配,则弹出栈顶元素(左括号)
stack.pop();
} else {
// 若当前字符不是右括号,则加入栈中
stack.push(ch);
}
}
补充:为什么用
Deque而不是Stack类?Java 里的Stack是遗留类,继承了Vector,性能和设计都不好,官方推荐用Deque(双端队列)的LinkedList实现来模拟栈,push()对应入栈,pop()对应出栈,peek()对应取栈顶,完全满足需求。
4. 遍历完之后,还要判断什么?
遍历完所有字符,不代表一定有效!比如 (( 这种情况:两个左括号,遍历完栈里还有两个元素,说明左括号多了,没有对应的右括号闭合。所以最后必须判断:栈是否为空?
- 空:所有左括号都被正确闭合,返回 true
- 非空:有左括号没闭合,返回 false
对应代码第 30-32 行:
java
return stack.isEmpty();
第三步:用例子验证逻辑
例子 1:{[]}(有效)
- 长度 4(偶数),继续
- 遍历:
{:左括号,入栈 → 栈:[{][:左括号,入栈 → 栈:[{, []]:右括号,查映射对应[,栈顶是[,匹配,弹出 → 栈:[{]}:右括号,查映射对应{,栈顶是{,匹配,弹出 → 栈:空
- 遍历完栈空,返回 true ✅
例子 2:([)](无效)
- 长度 4(偶数),继续
- 遍历:
(:入栈 → 栈:[(][:入栈 → 栈:[(, []):右括号,查映射对应(,栈顶是[,不匹配 → 直接返回 false ❌
例子 3:(((无效)
- 长度 2(偶数),继续
- 遍历:两个
(都入栈 → 栈:[(, (] - 遍历完栈非空,返回 false ❌
例子 4:)(无效)
- 长度 1(奇数),直接返回 false ❌
完整代码
java
class Solution {
public boolean isValid(String s) {
int n = s.length();
//数组长度为奇数直接返回false
if (n % 2 == 1) {
return false;
}
// 哈希表的键为右括号,值为相同类型的左括号,用于快速判断括号是否匹配
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
// 若当前字符为 右括号
if (pairs.containsKey(ch)) {
// 若栈内为空 或 栈顶元素(左括号) 不是与当前右括号匹配的类型,直接返回false
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
// 匹配,则弹出栈顶元素(左括号)
stack.pop();
} else {
// 若当前字符不是右括号,则加入栈中
stack.push(ch);
}
}
// 遍历完成,若栈内为空,则返回true,若栈内还剩余元素,则说明不符合返回false
return stack.isEmpty();
}
}
复杂度分析
- 时间复杂度:O (n),只遍历了一次字符串,每个字符入栈 / 出栈最多一次
- 空间复杂度:O (n),最坏情况(全是左括号)栈里存 n 个元素