【力扣100题】82.有效的括号
2026/6/12 19:13:32 网站建设 项目流程

一、题目描述

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合
  2. 左括号必须以正确的顺序闭合
  3. 每个右括号都有一个对应的相同类型的左括号

示例:

示例输入输出
示例1s = “()”true
示例2s = “()[]{}”true
示例3s = “(]”false
示例4s = “([])”true
示例5s = “([)]”false

提示:

  • 1 <= s.length <= 10^4
  • s 仅由括号 ‘()[]{}’ 组成

二、解题思路总览

拿到这道题,首先思考:什么时候括号是"有效的"?

想象一下括号匹配的过程:

  • 遇到左括号 → 它期待一个配对的右括号
  • 遇到右括号 → 必须和最近的那个左括号配对

这正是栈(Stack)的经典应用场景:后进先出(LIFO),最近未匹配的那个左括号,恰好应该和当前遇到的右括号配对。

核心思路:

步骤操作说明
1遍历字符串s的每个字符从左到右依次处理
2如果是左括号 ‘(’ ‘[’ ‘{’入栈,等待配对
3如果是右括号 ‘)’ ‘]’ ‘}’栈顶元素出栈并检查是否匹配
4遍历结束栈为空 → true;栈非空 → false

为什么用栈?

对比不用栈(错误思路)用栈(正确思路)
匹配方式盲目查找相同字符只看最近的一个左括号
顺序要求无法保证"正确的顺序"栈天然保证LIFO,匹配顺序正确
时间复杂度O(n^2) 或更差O(n)

三、完整代码(方法一:栈 + STL)

classSolution{public:boolisValid(string s){intn=s.size();// 奇数长度不可能配对成功if(n%2)returnfalse;stack<char>st;for(inti=0;i<n;i++){// 左括号:入栈if(s[i]=='('||s[i]=='['||s[i]=='{'){st.push(s[i]);}else{// 右括号:栈空则匹配失败if(st.empty())returnfalse;chartop=st.top();// 检查栈顶左括号是否与当前右括号配对if((s[i]==')'&&top=='(')||(s[i]==']'&&top=='[')||(s[i]=='}'&&top=='{')){st.pop();// 配对成功,出栈}else{returnfalse;// 配对失败}}}// 栈空说明全部配对成功returnst.empty()?true:false;}};

四、其他解法

方法二:数组模拟栈(手动实现,消除STL开销)
classSolution{public:boolisValid(string s){intn=s.size();if(n%2)returnfalse;// 用数组模拟栈,手动管理指针charst[n];inttop=0;// 栈顶指针,指向下一个待写入位置for(inti=0;i<n;i++){if(s[i]=='('||s[i]=='['||s[i]=='{'){st[top++]=s[i];// 入栈}else{if(top==0)returnfalse;// 栈空charc=st[--top];// 出栈if((s[i]==')'&&c!='(')||(s[i]==']'&&c!='[')||(s[i]=='}'&&c!='{')){returnfalse;}}}returntop==0;}};

改进点:用数组替代std::stack,避免STL的函数调用开销和额外的内存分配。


方法三:栈 + HashMap 配对(代码更简洁)
classSolution{public:boolisValid(string s){intn=s.size();if(n%2)returnfalse;stack<char>st;// 用哈希表存储配对关系,代码更清晰unordered_map<char,char>pair={{')','('},{']','['},{'}','{'}};for(charc:s){if(pair.count(c)){// c 是右括号,检查栈顶是否匹配if(st.empty()||st.top()!=pair[c]){returnfalse;}st.pop();}else{// c 是左括号,入栈st.push(c);}}returnst.empty();}};

改进点:unordered_map替代硬编码的 if-else,配对关系一目了然,扩展性更好(比如增加其他括号类型)。


方法四:计数法(仅适用于相邻括号配对)
classSolution{public:boolisValid(string s){intn=s.size();if(n%2)returnfalse;// 统计左括号数量,用于剪枝intcnt_p=0,cnt_s=0,cnt_c=0;for(charc:s){if(c=='(')cnt_p++;elseif(c=='[')cnt_s++;elseif(c=='{')cnt_c++;elseif(c==')')cnt_p--;elseif(c==']')cnt_s--;elseif(c=='}')cnt_c--;// 剪枝:任意计数器为负,说明右括号多于左括号if(cnt_p<0||cnt_s<0||cnt_c<0)returnfalse;}returncnt_p==0&&cnt_s==0&&cnt_c==0;}};

适用范围:此方法只能检测"相邻括号配对"(如"()[]{}"),无法处理嵌套和交叉(如"([])"正确返回 true,但"(]""([)]"也会返回 true)。仅作思路拓展,生产代码不推荐


方法五:字符串替换(直观但低效)
classSolution{public:boolisValid(string s){intn=s.size();if(n%2)returnfalse;// 不断删除相邻的有效括号对,直到无法删除while(s.find("()")!=string::npos||s.find("[]")!=string::npos||s.find("{}")!=string::npos){s.erase(s.find("()"),2);s.erase(s.find("[]"),2);s.erase(s.find("{}"),2);}returns.empty();}};

问题:find()是 O(n),erase()是 O(n),整体时间复杂度退化到O(n^3),不可用于生产环境。仅作思路展示


五、算法流程图

以输入s = "([])"为例,逐步展示算法执行过程:

Step 0: 初始状态 s = "( [] )" stack: [空] Step 1: 遇到 '(',左括号入栈 s = "( [] )" ^ stack: [ ( ] Step 2: 遇到 '[',左括号入栈 s = "( [] )" ^ stack: [ ( , [ ] Step 3: 遇到 ']',右括号 - 栈顶是 '[',匹配成功 - pop s = "( [] )" ^ stack: [ ( ] Step 4: 遇到 ')',右括号 - 栈顶是 '(',匹配成功 - pop s = "( [] )" ^ stack: [空] Step 5: 遍历结束 stack为空 → 返回 true

再来看一个失败案例s = "([)]"

Step 0: 初始状态 s = "( [ ) ]" stack: [空] Step 1: 遇到 '(',入栈 s = "( [ ) ]" ^ stack: [ ( ] Step 2: 遇到 '[',入栈 s = "( [ ) ]" ^ stack: [ ( , [ ] Step 3: 遇到 ')',右括号 - 栈顶是 '[',但 ')' 应该匹配 '(' - 不匹配!返回 false s = "( [ ) ]" ^ stack: [ ( , [ ] 最终结果: false

六、逐行解析(方法一)

intn=s.size();if(n%2)returnfalse;

剪枝优化:如果字符串长度是奇数,必然有未配对的括号,直接返回 false。这一步看似简单,但可以跳过大量无效计算。


stack<char>st;

数据结构选择:使用std::stack,只关心栈顶元素和入栈/出栈操作。


for(inti=0;i<n;i++){if(s[i]=='('||s[i]=='['||s[i]=='{'){st.push(s[i]);}

左括号处理:直接入栈,等待之后可能的配对。


}else{if(st.empty())returnfalse;

右括号处理的第一步:如果栈已经空了,说明没有对应的左括号,直接失败。


chartop=st.top();if((s[i]==')'&&top=='(')||(s[i]==']'&&top=='[')||(s[i]=='}'&&top=='{')){st.pop();}else{returnfalse;}}}

配对检查:核心逻辑。当前右括号必须与栈顶的左括号类型一致,才算配对成功。


returnst.empty()?true:false;

最终判断:遍历结束后,栈空说明所有左括号都成功配对;栈非空说明有左括号没被匹配。


七、复杂度分析

各方法复杂度对比
方法时间复杂度空间复杂度说明
方法一:STL栈O(n)O(n)标准解法,稳定高效
方法二:数组模拟栈O(n)O(n)省去STL开销,极致性能
方法三:栈+HashMapO(n)O(n)代码简洁,配对关系清晰
方法四:计数法O(n)O(1)错误解法,仅适用于相邻配对
方法五:字符串替换O(n^3)O(n)不可用,复杂度太高

方法一详细复杂度分析

时间复杂度:O(n)

  • 只需遍历字符串一次
  • 每个字符最多入栈一次、出栈一次

空间复杂度:O(n)

  • 最坏情况:全部是左括号,如 “(((((”
  • 栈中最多存放 n/2 个元素(严谨分析),但渐进表示为 O(n)

补充说明:

情况栈中元素数量说明
全部左括号O(n)“(((((”
全部右括号O(1)第一步就返回false
交替出现O(1)“()()()” 每次配对后栈即空

八、面试追问 FAQ

问题回答要点
Q1: 为什么不使用队列?队列是FIFO(先进先出),而括号匹配需要LIFO。最近的左括号才应该被匹配,队列无法提供这个能力。
Q2: 可以不用栈吗?理论上可以用递归模拟栈,但会增加函数调用开销,且有栈溢出风险。不推荐。
Q3: 如果要返回具体错误信息怎么办?可以在返回false时增加额外逻辑:记录是"多余右括号"、“多余左括号"还是"类型不匹配”。
Q4: 如何处理带优先级的表达式求值?这是另一个经典问题,通常用两个栈:一个存操作数,一个存操作符。括号匹配只是其中一步。
Q5: 如果字符串很长(10^6级别)怎么办?当前算法O(n)已经最优,主要瓶颈可能在I/O。可以用内存映射或流式处理,但核心算法不变。
Q6: 三种栈的实现哪个更好?生产环境优先用方法一(STL栈),代码可读性高;如果在竞赛或极端性能场景,用方法二(数组模拟);方法三适合需要频繁扩展配对规则的场景。

九、相关题目

题号题目难度关联点
32最长有效括号困难栈的延伸应用
921使括号有效的最少添加中等栈的变形
1021删除最外层的括号简单栈的变形
1541平衡括号的最少插入中等栈的变形

推荐刷题顺序:

  1. 本题(20.有效的括号)→ 基础
  2. 921(使括号有效的最少添加)→ 变形
  3. 1021(删除最外层的括号)→ 变形
  4. 32(最长有效括号)→ 综合

十、总结

维度内容
考察知识点栈(Stack)的应用
难度简单
代码框架遍历 + 栈操作
关键技巧利用栈的LIFO特性,保证匹配顺序
边界情况奇数长度、栈空、右括号不匹配
变形题返回最小插入数、最长有效长度
推荐解法方法一(STL栈)或方法二(数组模拟)

一句话总结:括号匹配是栈的入门经典题,核心就是"最近未匹配的左括号 = 当前右括号应该匹配的对象",用栈的LIFO特性天然实现这个逻辑。生产代码推荐使用方法一(STL栈),代码可读性高且性能足够。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询