20. 有效括号(Valid Parentheses)

难度:简单 | 主题:栈、字符串

题目

给定一个只包含 '('')''{''}''['']' 的字符串,判断字符串是否有效。有效需满足:

  1. 左括号必须用相同类型的右括号闭合

  2. 左括号必须以正确的顺序闭合

示例:

 
输入:s = "()[]{}"
 
输出:True
 
输入:s = "([)]"
 
输出:False
 

思路

先讲个故事:排队打饭的插队恶霸

想象食堂打饭窗口,学生们排成一队。突然来三个恶霸:“我要插队,而且我必须站在我朋友后面。”

一号恶霸说:“我站在最前面。我朋友站着说话不腰疼,说想插队的时候再叫我。” 这时二号来了:“我也一样。”

然后三号来了:“同上。”

这时一号的朋友来了:“该我插了!” 他必须找到一号——也就是最后一个被提到的朋友。最晚来的朋友最先被叫到。

这就是后进先出(LIFO),也就是栈的精髓。括号匹配也一样——最后一个左括号必须最先被右括号匹配


引导式推导:从手动匹配到自动栈

手动匹配思路:

遍历字符串,遇到左括号就记下来,遇到右括号就看它能不能配对上最近那个左括号。

 
"({[]})"
 
遇到 '(' → 记下
 
遇到 '{' → 记下
 
遇到 '[' → 记下
 
遇到 ']' → 和最近记下的 '[' 匹配 ✓,擦掉 '['
 
遇到 '}' → 和最近记下的 '{' 匹配 ✓,擦掉 '{'
 
遇到 ')' → 和最近记下的 '(' 匹配 ✓,擦掉 '('
 
全部擦完 → 有效 ✓
 

发现了吗?“最近记下”就是栈顶,“擦掉”就是出栈。


graph LR

    subgraph 入栈

        A["遇到 '('"] --> B["栈: ['(']"]

        B --> C["遇到 '{'"] --> D["栈: ['(','{']"]

        D --> E["遇到 '['"] --> F["栈: ['(','{','[']"]

    end

    subgraph 出栈匹配

        F --> G["遇到 ']'"]

        G --> H["'[' == 栈顶 '[' ✓<br/>出栈 → ['(','{']"]

        H --> I["遇到 '}'"]

        I --> J["'{' == 栈顶 '{' ✓<br/>出栈 → ['(']"]

        J --> K["遇到 ')'"]

        K --> L["'(' == 栈顶 '(' ✓<br/>出栈 → []"]

    end

关键设计:用字典映射右括号到左括号

 
pairs = {')': '(', ']': '[', '}': '{'}
 

这样遇到右括号时,直接查字典就知道它该匹配谁,不用写三个 if-else。


边界三步走

三种导致 False 的场景,实践中要全部覆盖:

  1. 栈空遇右括号 → 没有对应的左括号 → False

  2. 栈顶不匹配 → 括号类型不对 → False

  3. 遍历完栈非空 → 左括号多了 → False

优化剪枝:字符串长度为奇数 → 直接 False(不可能完全匹配)。


推荐写法

 
def isValid(s):
 
    if len(s) % 2 == 1:        # 奇数长度直接排除
 
        return False
 
    pairs = {')': '(', ']': '[', '}': '{'}  # 右括号→左括号映射
 
    stack = []                 # 栈:存储未匹配的左括号
 
    for ch in s:
 
        if ch in pairs:        # 遇到右括号
 
            if not stack or stack[-1] != pairs[ch]:  # 栈空或不匹配
 
                return False
 
            stack.pop()        # 匹配成功,弹出栈顶左括号
 
        else:
 
            stack.append(ch)   # 左括号入栈
 
    return not stack           # 栈空说明全部匹配
 

复杂度

指标解释
时间O(n)遍历字符串一次
空间O(n)最坏情况全是左括号,栈存 n 个

实战考量

频率分析

出现在:约 80% 的公司会在一面出这道题做热身,字节/百度/美团都考。LeetCode 前 20 高频题,栈的”Hello World”。重点是代码简洁度和边界完整性

延伸思考

Q:如果括号类型很多(比如 10 种)呢?

A:字典配对依然适用,扩展性 O(1)。只需要在字典里加新映射,核心逻辑零改动。

Q:如果要求返回最长有效括号子串的长度呢?

A:动态规划或栈记录下标(32 题,困难)。栈里存索引而不是括号本身,遇到匹配时计算长度。

Q:最小添加次数使括号有效(921 题)?

A:统计未匹配的左括号数(need)和右括号数(右括号无左匹配)。不需要栈,一个变量即可。

Q:不用栈只用变量能判断吗?

A:只有一种括号类型时可以(计数),但有多种类型时需要栈来维护顺序信息。

易错点

  • 忘记判断栈空就访问栈顶

  • 遍历完后忘记检查栈是否为空(有多余左括号)

  • 字典 key 和 value 写反(左括号映射到右括号)


生活类比

有效括号 → 套娃开合

每一个左括号就是打开一个套娃,右括号就是合上它。

打开的套娃必须最先合上——这就是栈。

如果你合上时发现套娃对不上号(括号类型不匹配)→ 无效。

如果到最后还有没合上的套娃 → 无效。

栈不是括号的特权——但凡有嵌套结构的地方,都是栈的舞台。


相关题目

题目关系
394字符串解码栈 + 嵌套结构处理
22括号生成括号类问题的另一面

→ 返回题单:LeetCode学习路线图 > 三、栈与队列

速记卡(面试闪卡)

Q1:一句话讲清「20. 有效括号(Valid Parentheses)」到底是什么?

A:用栈判断字符串里的括号是否都能按正确类型和顺序闭合。

Q2:题目 —— 怎么理解?

A:像插队恶霸:给你一串只包含 (){}[] 的字符,判断是否有效——左括号要用同类型右括号闭合,且顺序要对(如 ([)] 是错的)。示例 ”()[]{} ” 真、”([)]” 假。

Q3:思路 —— 怎么理解?

A:像套娃开合:遇到左括号就入栈,遇到右括号就看它能不能配对上栈顶那个最近的左括号(用字典把右括号映射到左括号,一步查表)。栈空遇右括号、栈顶不匹配、遍历完栈非空——三种情况都判 False;长度为奇数直接排除。

Q4:推荐写法 —— 怎么理解?

A:长度奇数先 return False;遍历字符,右括号(在字典里)就检查栈空或不匹配→False 并 pop,左括号 append 入栈;最后 return not stack(栈空才全匹配)。口诀:字典映射 + 栈存左括号 + 末尾查栈空。

Q5:复杂度 —— 怎么理解?

A:像过一遍字符串:时间 O(n) 只遍历一次;空间 O(n) 最坏全是左括号、栈存 n 个。括号类型再多,字典照样 O(1) 扩展,核心逻辑零改动。

Q6:核心速记主线有哪些?

  • 栈存未匹配的左括号,右括号查字典配对栈顶

  • 三种 False:栈空遇右、栈顶不匹配、遍历完栈非空

  • 奇数长度直接 False,遍历完 return not stack

  • 栈是嵌套结构的通用解法,不止括号

口诀

A:左括号入栈,

右括号对栈顶;

类型不对就 False,

栈空才匹配。

相关链接