题目看是算法题,说白了就是给定一个只包含 ( ) { } [ ] 的字符串,让你判断括号的嵌套顺序合不合法。这类问题在 LeetCode 上编号 20,属于栈结构最经典的入门题,很多人把这个当作“每日一练”的第一题,我当初也是这样。今天就把这题的完整拆解思路、多种代码写法、踩坑记录和可以扩散的考点串起来聊聊,文章不是只给你贴一段能过测试的答案,而是希望你看完之后,以后再遇到“符号配对”“嵌套校验”这类问题时能直接迁移思路。
先说清楚,这道题的目标是“有效括号”。有效不只是左右括号数量对得上,顺序也得对。比如 () 有效,但 (] 无效;{[]} 有效,但 ([)] 就是无效的。判断逻辑本身不复杂,难点在于你怎么把“嵌套顺序”翻译成代码逻辑,而这背后牵扯出一个很核心的工具:栈。
1. 题目本质:表面考配对,实际考栈
1.1 先明确题目要求与输入特征
原题是这样描述的:给定一个只包括 (、)、{、}、[、] 的字符串 s,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合;左括号必须以正确的顺序闭合。这里有个很容易被忽略的点:题目只给了这六种字符,不会出现小写字母、数字、空格,所以不需要做字符过滤。
输入可能的各种形态可以分成几类,我实际测试时会刻意构造这些用例:
- 空字符串:
""是有效的,因为不存在未闭合的括号。 - 单种连续型:
"((((("、")))",这个主要测计数和栈顶判断。 - 简单嵌套型:
"([])"、"{[]}"是有效,因为括号一层套一层,顺序合理。 - 交叉型:
"([)]",这个最迷惑人,左右数量各两个,但顺序不对,必须判无效。 - 边界堆叠型:
"(([]){})"这种混合嵌套,需要程序一步一步追踪。 - 干扰型:很多变体题会加入字母,比如
"a(b[c]d)",不过原题没有,如果想扩写,可以在代码里加字符过滤。
为什么先要讲输入特征?因为写判断逻辑前,你把所有可能形态列清楚,后面设计循环分支会轻松很多。
1.2 为什么第一直觉一定是栈
很多没有系统学过数据结构的人,第一想法是用三个计数器分别记录三种括号的数量,左括号加一,右括号减一,最后三个都归零就认为合法。这个思路用来解只有一种括号的简化版是没问题的,但对于三种括号同时存在的情况,它完全不能处理排序关。举个最直接的例子:输入是 ([)],用计数器看,左括号数量等于右括号数量,三个类型各自配对,如果只按计数器判断会误判为有效,但实际上右括号 ) 出现时,最近一个未匹配的左括号是 [,类型对不上,所以这个字符串本质上就成了括号交叉嵌套,当然非法。
栈为什么适合?因为它的特性是后进先出。括号配对本身就有对称性:最早出现的左括号往往要等最晚出现;而最后一个出现的左括号,会在下一个右括号出现时立刻被匹配。这恰恰符合栈的“弹栈顺序”。用行话说,处理字符串时,凡是遇到左括号就压栈,遇到右括号就出栈并对比“栈顶元素是否和当前右括号匹配”,如果匹配不上就直接返回错误,整个过程一次扫描就能完成。时间复杂度 O(n),空间复杂度最坏也是 O(n)。
栈解决括号匹配,几乎就是教科书级别的“数据结构与场景匹配”案例。我在实际做这种题时,第一直觉不是去想各种奇技淫巧,而是先问自己:当前这个问题的状态,天然叠了一层“后出现先匹配”的结构,那不管题目怎么包装,本质上就是在考栈。你看正则表达式里括号捕获、编译器检查括号配对、XML/HTML 标签闭合,全是一个逻辑:遇到开始符号入栈,遇到结束符号出栈。理解这道题,就相当于理解了一批工程场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用栈实现的标准做法与代码拆解
2.1 算法流程和栈操作顺序
先给出一版最简单的流程,适合在纸上把所有细节推演清楚:
- 预处理:如果字符串长度是奇数,直接返回 false,因为合法成对括号总长度一定是偶数。
- 初始化一个空栈。
- 从左到右遍历字符串的每个字符
c。 - 如果
c是左括号,即{、[、(中的任意一个,就把它压入栈。 - 如果
c是右括号,分两种情况:- 如果此时栈为空,说明当前右括号没有对应的左括号,直接返回 false。
- 如果栈非空,弹出栈顶元素
top,检查top是否与c匹配。
- 字符串遍历完成后,如果栈为空,说明所有左括号都有了匹配,返回 true;如果栈里还有残留左括号,说明有些括号没闭合,返回 false。
看上去很简单,容易写挂的点其实不少。最经典的就是只记得弹栈,却不做栈空检查,代码写成 if (stack.peek() != pair) return false,一旦遇到 "]" 开头且栈为空,就抛 EmptyStackException。还有一个点是匹配表的方向写反,比如把右括号当 key、左括号当 value,然后弹栈后去查,逻辑也能成立,但前提是你查的是当前右括号,不是弹出来的栈顶,这个顺序一定要捋顺。
我这里推荐的流程是“右括号查栈顶”,意思是当前字符是右括号时,用 map 查出它期望匹配的左括号,再和栈顶的左括号对比。也有人反过来用左括号查右括号,就是把压栈动作改成压右括号,遇到右括号再弹出来对比,这两种写法最后都能过,根据习惯来即可,关键是别混。
2.2 配合哈希映射的 Java 实现
第一种是 Java 实现,配合 HashMap 存储左右括号匹配关系,代码清晰度最高,推荐阅读时使用。
java复制import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
class Solution {
public boolean isValid(String s) {
if (s.length() % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(')
